Greasy Fork is available in English.
Chat with PI on pi.ai using this script that enables speech recognition.
// ==UserScript==
// @name PI - no hands talk :)
// @namespace http://tampermonkey.net/
// @version 0.1.3
// @description Chat with PI on pi.ai using this script that enables speech recognition.
// @author Guki
// @match https://pi.ai/talk
// @icon https://www.google.com/s2/favicons?sz=64&domain=pi.ai
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// if you have a job for a person who interested in ML, AI agents, LLMs
// please contact me, my email: [email protected]
// options
const sendDelay = 3000 // time in ms after last registered spoken word before sending a message
const startActive = false // activate the script on start
const checkRecognition = 1000 // time in ms of how often to check recognition status to restart it if its become inactive
let SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = new SpeechRecognition();
recognition.interimResults = true;
let recognitionActive = false;
if (startActive) {
recognitionActive = true
}
recognition.onstart = function() {
recognitionActive = true;
};
recognition.onend = function() {
recognitionActive = false;
if (scriptActive) {
setTimeout(() => {
// rarely might be active by the interval
if (!recognitionActive) {
recognition.start()
}
}, 5)
}
};
let scriptActive = false
if (startActive) {
scriptActive = true
recognition.start();
}
// script toggle button
const button = document.createElement('button');
button.style.position = 'fixed';
button.style.top = '120px';
button.style.left = '24px';
button.style.borderRadius = '50%';
button.style.width = '42px';
button.style.height = '42px';
button.style.zIndex = '9999';
button.style.color = 'white';
button.style.opacity = '0.8';
if (startActive) {
button.style.background = '#e63946';
} else {
button.style.background = '#a8dadc';
}
button.style.paddingLeft = "5px";
button.style.transition = "all 200ms"
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8"><path d="M8.25 4.5a3.75 3.75 0 117.5 0v8.25a3.75 3.75 0 11-7.5 0V4.5z" /><path d="M6 10.5a.75.75 0 01.75.75v1.5a5.25 5.25 0 1010.5 0v-1.5a.75.75 0 011.5 0v1.5a6.751 6.751 0 01-6 6.709v2.291h3a.75.75 0 010 1.5h-7.5a.75.75 0 010-1.5h3v-2.291a6.751 6.751 0 01-6-6.709v-1.5A.75.75 0 016 10.5z" /></svg>'
button.onclick = function() {
if (scriptActive) {
scriptActive = false
button.style.background = '#a8dadc';
} else {
scriptActive = true
button.style.background = '#e63946';
}
if (!scriptActive && recognitionActive) {
recognition.stop();
} else if (scriptActive && !recognitionActive) {
recognition.start();
}
};
document.body.appendChild(button);
// infinite recognition restart when script is active
setInterval(function() {
if (scriptActive && !recognitionActive) {
recognition.start();
}
}, checkRecognition);
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
let completeTranscript = ''
function cleanCompleteTranscript() {
completeTranscript = ''
}
function pressEnter() {
var textarea = document.querySelector('.block.w-full.resize-none.overflow-y-hidden.whitespace-pre-wrap.bg-transparent');
var enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true,
cancelable: true
});
textarea.dispatchEvent(enterEvent);
cleanCompleteTranscript()
}
const debouncedPressEnter = debounce(pressEnter, sendDelay);
recognition.onresult = function(event) {
let textarea = document.querySelector('.block.w-full.resize-none.overflow-y-hidden.whitespace-pre-wrap.bg-transparent');
let transcript = event.results[0][0].transcript
if(!event.results[0].isFinal) {
// only visual
textarea.value = completeTranscript + ' ' + transcript;
} else {
textarea.value = completeTranscript + ' ' + transcript;
completeTranscript += ' ' + transcript
// value is not registered correctly without this workaround
textarea.value = ''
Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set.call(textarea, textarea.value + completeTranscript);
const inputEvent = new Event('input', { bubbles: true });
textarea.dispatchEvent(inputEvent);
}
debouncedPressEnter();
};
})();