您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Speak to Pi with OpenAI's Whisper
当前为
// ==UserScript== // @name Say, Pi // @namespace http://www.saypi.ai/ // @version 1.0.1 // @description Speak to Pi with OpenAI's Whisper // @author Ross Cadogan // @match https://pi.ai/* // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function () { 'use strict'; const localConfig = { webServerUrl: "http://localhost:3000", apiServerUrl: "http://localhost:5000", // Add other configuration properties as needed }; // Define a global configuration property const config = { webServerUrl: "https://www.saypi.ai", apiServerUrl: "https://api.saypi.ai", // Add other configuration properties as needed }; // Create a MutationObserver to listen for changes to the DOM var observer = new MutationObserver(function (mutations) { // Check each mutation for (var i = 0; i < mutations.length; i++) { var mutation = mutations[i]; // If nodes were added, check each one if (mutation.addedNodes.length > 0) { for (var j = 0; j < mutation.addedNodes.length; j++) { var node = mutation.addedNodes[j]; // If the node is the appropriate container element, add the button and stop observing if (node.nodeName.toLowerCase() === 'div' && node.classList.contains('fixed') && node.classList.contains('bottom-16')) { var footer = node; var buttonContainer = footer.querySelector('.relative.flex.flex-col'); if (buttonContainer) { addAudioButton(buttonContainer); } else { console.log('No button container found in footer'); } observer.disconnect(); return; } } } } }); function injectScript(callback) { return injectScriptRemote(callback); } function injectScriptRemote(callback) { // Get the URL of the remote script var remoteScriptUrl = config.webServerUrl + '/static/js/literal.js'; GM_xmlhttpRequest({ method: "GET", url: remoteScriptUrl, onload: function (response) { var scriptElement = document.createElement("script"); scriptElement.type = "text/javascript"; scriptElement.id = 'saypi-script'; const configText = 'var config = ' + JSON.stringify(config) + ';'; scriptElement.textContent = configText + response.responseText; document.body.appendChild(scriptElement); // Call the callback function after the script is added if (callback) { callback(); } } }); } function injectScriptLocal(callback) { var scriptElement = document.createElement("script"); scriptElement.type = "text/javascript"; scriptElement.id = 'saypi-script'; const scriptText = ` // Paste the contents of static/js/literal.js here to avoid CORS issues ` const configText = 'var config = ' + JSON.stringify(config) + ';'; scriptElement.textContent = configText + scriptText; document.body.appendChild(scriptElement); // Call the callback function after the script is added if (callback) { callback(); } } function addAudioButton(container) { var button = document.createElement('button'); button.id = 'talkButton'; button.type = 'button'; button.className = 'relative flex mt-1 mb-1 rounded-full px-2 py-3 text-center bg-cream-550 hover:bg-cream-650 hover:text-brand-green-700 text-muted'; // Set ARIA label and tooltip button.setAttribute('aria-label', 'Talk (Press Control + Space to use hotkey)'); button.setAttribute('title', 'Talk (Press Control + Space to use hotkey)'); container.appendChild(button); addAudioButtonStyles(); addAudioIcon(button); // Call the function to inject the script after the button has been added injectScript(registerAudioButtonEvents); } function addAudioIcon(button) { var iconHtml = ` <svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 56.25 30" class="waveform"> <defs> <clipPath id="a"> <path d="M.54 12H3v5H.54Zm0 0"/> </clipPath> <clipPath id="b"> <path d="M25 2.2h2v24.68h-2Zm0 0"/> </clipPath> <clipPath id="c"> <path d="M53 12h1.98v5H53Zm0 0"/> </clipPath> </defs> <g clip-path="url(#a)"> <path fill="#776d6d" d="M1.48 12.71c-.5 0-.9.4-.9.9v1.85a.9.9 0 0 0 1.8 0v-1.84c0-.5-.4-.9-.9-.9Zm0 0"/> </g> <path fill="#776d6d" d="M4.98 6.63c-.5 0-.9.4-.9.9v14.01a.9.9 0 0 0 1.81 0v-14c0-.5-.4-.92-.9-.92Zm3.51 3.1a.9.9 0 0 0-.9.91v7.79a.9.9 0 0 0 1.8 0v-7.79c0-.5-.4-.9-.9-.9ZM12 3.83a.9.9 0 0 0-.91.9v19.6a.9.9 0 0 0 1.8 0V4.74c0-.5-.4-.9-.9-.9Zm3.5 8.29a.9.9 0 0 0-.91.9v3.03a.9.9 0 0 0 1.81 0v-3.03c0-.5-.4-.9-.9-.9ZM19 6.8c-.5 0-.9.4-.9.9v13.68a.9.9 0 0 0 1.8 0V7.7c0-.5-.4-.9-.9-.9Zm3.58-2.97h-.01c-.5 0-.9.4-.9.9l-.13 19.6c0 .5.4.9.9.91.5 0 .9-.4.9-.9l.14-19.6a.9.9 0 0 0-.9-.9Zm0 0"/> <g clip-path="url(#b)"> <path fill="#776d6d" d="M26 2.2c-.5 0-.9.4-.9.9v22.86a.9.9 0 1 0 1.81 0V3.11a.9.9 0 0 0-.9-.91Zm0 0"/> </g> <path fill="#776d6d" d="M29.52 7.71a.9.9 0 0 0-.91.9v11.85a.9.9 0 0 0 1.81 0V8.62c0-.5-.4-.9-.9-.9Zm3.5 2.93a.9.9 0 0 0-.9.91v5.97a.9.9 0 0 0 1.8 0v-5.97c0-.5-.4-.9-.9-.9Zm3.5-5.78c-.5 0-.9.4-.9.9v17.55a.9.9 0 0 0 1.81 0V5.76c0-.5-.4-.9-.9-.9Zm3.51 3.34c-.5 0-.9.4-.9.9v10.87a.9.9 0 0 0 1.8 0V9.1a.9.9 0 0 0-.9-.91Zm3.5 3.08c-.5 0-.9.4-.9.91v4.7a.9.9 0 1 0 1.8 0v-4.7a.9.9 0 0 0-.9-.9Zm3.51-7.45a.9.9 0 0 0-.91.9v19.6a.9.9 0 0 0 1.81 0V4.74c0-.5-.4-.9-.9-.9Zm3.5 5.57a.9.9 0 0 0-.9.91v8.45a.9.9 0 0 0 1.8 0v-8.45c0-.5-.4-.9-.9-.9Zm0 0"/> <g clip-path="url(#c)"> <path fill="#776d6d" d="M54.04 12.96a.9.9 0 0 0-.9.91v1.33a.9.9 0 1 0 1.8 0v-1.32a.9.9 0 0 0-.9-.92Zm0 0"/> </g> </svg> `; var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); button.appendChild(icon); icon.outerHTML = iconHtml; } function addStyles(css) { const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } function addAudioButtonStyles() { // Get the button and register for mousedown and mouseup events var button = document.getElementById('talkButton'); button.style.marginTop = '0.25rem'; button.style.borderRadius = '18px'; button.style.width = '120px'; // button animation addStyles(` @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(0.9); } 100% { transform: scale(1); } } #talkButton:active .waveform, #talkButton.active .waveform { animation: pulse 1s infinite; } `); } function registerAudioButtonEvents() { var button = document.getElementById('talkButton'); button.addEventListener('mousedown', function () { idPromptTextArea(); unsafeWindow.startRecording(); }); button.addEventListener('mouseup', function () { unsafeWindow.stopRecording(); }); registerHotkey(); // "warm up" the microphone by acquiring it before the user presses the button document.getElementById('talkButton').addEventListener('mouseenter', setupRecording); document.getElementById('talkButton').addEventListener('mouseleave', tearDownRecording); window.addEventListener('beforeunload', tearDownRecording); } function registerHotkey() { // Register a hotkey for the button let ctrlDown = false; document.addEventListener('keydown', function (event) { if (event.ctrlKey && event.code === 'Space' && !ctrlDown) { ctrlDown = true; // Simulate mousedown event let mouseDownEvent = new Event('mousedown'); document.getElementById('talkButton').dispatchEvent(mouseDownEvent); talkButton.classList.add('active'); // Add the active class } }); document.addEventListener('keyup', function (event) { if (ctrlDown && event.code === 'Space') { ctrlDown = false; // Simulate mouseup event let mouseUpEvent = new Event('mouseup'); document.getElementById('talkButton').dispatchEvent(mouseUpEvent); talkButton.classList.remove('active'); } }); } function idPromptTextArea() { var textarea = document.getElementById('prompt'); if (!textarea) { // Find the first <textarea> element and give it an id var textareaElement = document.querySelector('textarea'); if (textareaElement) { textareaElement.id = 'prompt'; } else { console.log('No <textarea> element found'); } } } // Start observing the entire document for changes to child nodes and subtree observer.observe(document, { childList: true, subtree: true }); })();