PI.ai Voice

Adds voice option in PI.ai responses

// ==UserScript==
// @name         PI.ai Voice
// @namespace    https://github.com/HellFiveOsborn
// @version      2024-05-29
// @description  Adds voice option in PI.ai responses
// @author       HellFive Osborn
// @match        https://pi.ai/threads
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pi.ai
// @compatible          chrome
// @compatible          firefox
// @compatible          edge
// @compatible          opera
// @compatible          brave
// @compatible          vivaldi
// @compatible          waterfox
// @compatible          librewolf
// @compatible          ghost
// @compatible          qq
// @compatible          whale
// @compatible          kiwi
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const style = document.createElement('style');
    style.textContent = `
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .animate-spin {
            animation: spin 1s linear infinite;
        }
    `;
    document.head.appendChild(style);

    const originalFetch = window.fetch;
    let audio = null;
    let preferredVoice;

    // set the default value for preferredVoice in localStorage
    if (!localStorage.getItem('preferredVoice')) {
        localStorage.setItem('preferredVoice', 'voice1');
    }

    window.fetch = function(...args) {
        const url = args[0];
        const options = args[1] || {};

        // retrieve the preferredVoice value from localStorage
        preferredVoice = localStorage.getItem('preferredVoice');

        if (typeof url === 'string' && url.startsWith("/api/chat/start") || url.startsWith("/api/user/settings") && (!options.method || options.method.toUpperCase() === 'POST')) {
            return originalFetch.apply(this, arguments).then(response => {
                const clonedResponse = response.clone();
                clonedResponse.text().then(text => {
                    // parse the response text as JSON and update the preferredVoice value if necessary
                    const data = JSON.parse(!url.startsWith("/api/user/settings") ? text : options.body);
                    if (data.preferredVoice && data.preferredVoice !== preferredVoice) {
                        preferredVoice = data.preferredVoice;
                        localStorage.setItem('preferredVoice', preferredVoice);
                    }
                });
                return response;
            });
        }

        if (typeof url === 'string' && url.startsWith("/api/chat/history?conversation=") && (!options.method || options.method.toUpperCase() === 'GET')) {
            return originalFetch.apply(this, arguments).then(response => {
                const clonedResponse = response.clone();
                clonedResponse.text().then(text => {
                    let { messages } = JSON.parse(text);
                    let AIMessages = messages.filter(item => item.direction === "outbound");
                    window.AIMessages = AIMessages.reverse();
                    processNewElements();
                });
                return response;
            });
        }
        return originalFetch.apply(this, arguments);
    };

    function createButton(sid) {
        let button = `<button id="voiceButton" class="w-8 h-8 p-2 flex z-10 items-center text-neutral-800 inset-0 bg-neutral-200 mt-4" style="border-radius:9999px;" type="button" data-sid="${sid}"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5 text-primary-700 lg:h-6 lg:w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: currentColor; --darkreader-inline-stroke: currentColor;"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 001.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06zM18.584 5.106a.75.75 0 011.06 0c3.808 3.807 3.808 9.98 0 13.788a.75.75 0 11-1.06-1.06 8.25 8.25 0 000-11.668.75.75 0 010-1.06z"></path><path d="M15.932 7.757a.75.75 0 011.061 0 6 6 0 010 8.486.75.75 0 01-1.06-1.061 4.5 4.5 0 000-6.364.75.75 0 010-1.06z"></path></svg></button>`;
        let btn = document.createElement('div');
        btn.innerHTML = button;
        let buttonElement = btn.firstElementChild;
        buttonElement.addEventListener('click', () => handleButtonClick(buttonElement, sid));
        return buttonElement;
    }

    function handleButtonClick(button, sid) {
        if (audio && !audio.paused) {
            audio.pause();
            audio = null;
            button.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5 text-primary-700 lg:h-6 lg:w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: currentColor; --darkreader-inline-stroke: currentColor;"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 001.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06zM18.584 5.106a.75.75 0 011.06 0c3.808 3.807 3.808 9.98 0 13.788a.75.75 0 11-1.06-1.06 8.25 8.25 0 000-11.668.75.75 0 010-1.06z"></path><path d="M15.932 7.757a.75.75 0 011.061 0 6 6 0 010 8.486.75.75 0 01-1.06-1.061 4.5 4.5 0 000-6.364.75.75 0 010-1.06z"></path></svg>`;
            return;
        }

        button.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024" aria-hidden="true" class="h-5 w-5 animate-spin lg:h-6 lg:w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: currentColor; --darkreader-inline-stroke: currentColor;"><path d="M512 64C265.6 64 64 265.6 64 512s201.6 448 448 448 448-201.6 448-448S758.4 64 512 64zm0 820.8C308.8 884.8 139.2 715.2 139.2 512S308.8 139.2 512 139.2 884.8 308.8 884.8 512 715.2 884.8 512 884.8z" /><path d="M512 139.2V64c-247.2 0-448 200.8-448 448s200.8 448 448 448v-75.2c-204 0-372.8-168.8-372.8-372.8S308 139.2 512 139.2z" /></svg>`;

        fetch(`https://pi.ai/api/chat/voice?mode=eager&voice=${preferredVoice}&messageSid=${sid}`, {
            method: 'GET'
        })
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.blob();
        })
        .then(blob => {
            const url = URL.createObjectURL(blob);
            audio = new Audio(url);
            audio.play();

            button.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5 lg:h-6 lg:w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: currentColor; --darkreader-inline-stroke: currentColor;"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 001.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06zM17.78 9.22a.75.75 0 10-1.06 1.06L18.44 12l-1.72 1.72a.75.75 0 001.06 1.06l1.72-1.72 1.72 1.72a.75.75 0 101.06-1.06L20.56 12l1.72-1.72a.75.75 0 00-1.06-1.06l-1.72 1.72-1.72-1.72z"></path></svg>`;
            audio.onended = () => {
                button.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" aria-hidden="true" class="h-5 w-5 text-primary-700 lg:h-6 lg:w-6" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" data-darkreader-inline-fill="" data-darkreader-inline-stroke="" style="--darkreader-inline-fill: currentColor; --darkreader-inline-stroke: currentColor;"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 001.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06zM18.584 5.106a.75.75 0 011.06 0c3.808 3.807 3.808 9.98 0 13.788a.75.75 0 11-1.06-1.06 8.25 8.25 0 000-11.668.75.75 0 010-1.06z"></path><path d="M15.932 7.757a.75.75 0 011.061 0 6 6 0 010 8.486.75.75 0 01-1.06-1.061 4.5 4.5 0 000-6.364.75.75 0 010-1.06z"></path></svg>`;
            };
        })
        .catch(error => {
            console.error('There was a problem with the fetch operation:', error);
        });
    }

    function processNewElements() {
        if (!window.AIMessages) return;
        let AIContainers = window.document.querySelectorAll('.break-anywhere:not([class*=" "])');
        AIContainers.forEach((element, index) => {
            if (!element.querySelector('#voiceButton') && window.AIMessages[index]) {
                let sid = window.AIMessages[index].sid;
                let button = createButton(sid);
                let contentMenu = document.createElement('div');
                contentMenu.classList.add('flex', 'items-center', 'gap-2'); // add Tailwind CSS classes

                // move data-projection element to the new div
                let dataProjection = element.querySelector("div[data-projection-id]");
                if (dataProjection) {
                    contentMenu.appendChild(dataProjection);
                }

                // append button and contentMenu to the element
                contentMenu.appendChild(button);
                element.appendChild(contentMenu);
            }
        });
    }

    const observer = new MutationObserver((mutationsList) => {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                processNewElements();
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    function checkForUpdate() {
        var scriptUrl = 'https://update.gf.qytechs.cn/scripts/496302/PIai%20Voice.user.js';

        GM_xmlhttpRequest({
            method: 'GET',
            url: scriptUrl,
            headers: {
                'Accept': 'text/plain'
            },
            onload: function(response) {
                var lastModified = new Date(response.responseHeaders.get('Last-Modified')).getTime();

                var lastChecked = localStorage.getItem('PIaiVoiceLastChecked');

                if (!lastChecked || lastModified > lastChecked) {
                    localStorage.setItem('PIaiVoiceLastChecked', lastModified);
                    GM_notification({
                        text: 'An update to the PI.ai Voice script is available on GreasyFork. Visit <https://gf.qytechs.cn/scripts/496302-PIai-Voice> for more information.',
                        title: 'PI.ai Voice: Update available 🚀',
                        timeout: 10000
                    });
                }
            },
            onerror: function() {
                console.error('Failed to check for updates to the PI.ai Voice script.');
            }
        });
    }

    // Calls checkForUpdate() function every 24 hours
    setInterval(checkForUpdate, 24 * 60 * 60 * 1000);

    console.log("Fetch request interceptor and MutationObserver have been set up.");
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址