// ==UserScript== // @name PI - no hands talk :) // @namespace http://tampermonkey.net/ // @version 0.1 // @description Talk to PI on pi.ai with speech recognition. Works even when the browser is not active window, so you can talk to PI on background and do your thing. Messages are sended after a couple of seconds of non-talking automatically, time can be adjusted by the options at the beginning of the script. // @author Guki // @match https://pi.ai/talk?scr // @icon https://www.google.com/s2/favicons?sz=64&domain=pi.ai // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // if you have a job for a person who interested in ML, AI agents, LLMs // please contact me, my email: sokolovivanf@gmail.com // options const sendDelay = 4000 // 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 = '' 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); let transcript = "" recognition.onresult = function(event) { let textarea = document.querySelector('.block.w-full.resize-none.overflow-y-hidden.whitespace-pre-wrap.bg-transparent'); if(!event.results[0].isFinal) { // only visual transcript = event.results[0][0].transcript textarea.value = completeTranscript + ' ' + transcript; } else { completeTranscript += ' ' + transcript // take transcript from previous iteration, without punctiation // 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(); }; })();