Greasy Fork

Greasy Fork is available in English.

PI - no hands talk :)

Chat with PI on pi.ai using this script that enables speech recognition.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
    };
})();