T3 Chat STT with Whisper API (Configurable API Key)

Adds speech-to-text functionality to t3.chat using OpenAI's Whisper API with configurable API key via UI.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         T3 Chat STT with Whisper API (Configurable API Key)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds speech-to-text functionality to t3.chat using OpenAI's Whisper API with configurable API key via UI.
// @author       wearifulpoet
// @match        https://t3.chat/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // ********** API Key Configuration **********
    // Use localStorage to store the API key.
    // Initially, if no API key is stored, an empty string is returned.
    const getApiKey = () => localStorage.getItem("whisper_api_key") || "";
    const setApiKey = (key) => localStorage.setItem("whisper_api_key", key);
    // *********************************************

    // Create the STT button.
    const sttButton = document.createElement('button');
    sttButton.innerHTML = "🎙️ Start STT";
    sttButton.style.position = "fixed";
    sttButton.style.bottom = "20px";
    sttButton.style.right = "20px";
    sttButton.style.zIndex = "9999";
    sttButton.style.padding = "10px";
    sttButton.style.fontSize = "16px";
    document.body.appendChild(sttButton);

    // Create the API Key configuration button.
    const apiKeyButton = document.createElement('button');
    apiKeyButton.innerHTML = "API Key";
    apiKeyButton.style.position = "fixed";
    apiKeyButton.style.bottom = "20px";
    apiKeyButton.style.right = "140px";
    apiKeyButton.style.zIndex = "9999";
    apiKeyButton.style.padding = "10px";
    apiKeyButton.style.fontSize = "16px";
    document.body.appendChild(apiKeyButton);

    // Event listener to set/update the API key.
    apiKeyButton.addEventListener("click", () => {
        const currentKey = getApiKey();
        const newKey = prompt("Enter your Whisper API key:", currentKey);
        if (newKey !== null) {
            setApiKey(newKey.trim());
            alert("API key updated!");
        }
    });

    let mediaRecorder = null;
    let audioChunks = [];
    let isRecording = false;

    sttButton.addEventListener("click", async () => {
        if (!isRecording) {
            // Check if the API key has been set.
            const storedKey = getApiKey();
            if (!storedKey) {
                alert("Please set your Whisper API key using the API Key button.");
                return;
            }
            // Start recording
            sttButton.innerHTML = "Stop Recording";
            isRecording = true;
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                mediaRecorder = new MediaRecorder(stream);
                audioChunks = [];

                mediaRecorder.addEventListener("dataavailable", event => {
                    if (event.data.size > 0) {
                        audioChunks.push(event.data);
                    }
                });

                mediaRecorder.addEventListener("stop", async () => {
                    const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                    // Prepare the file to send to Whisper API.
                    const audioFile = new File([audioBlob], "audio.wav", { type: 'audio/wav' });
                    const formData = new FormData();
                    formData.append("file", audioFile);
                    formData.append("model", "whisper-1");

                    try {
                        const response = await fetch("https://api.openai.com/v1/audio/transcriptions", {
                            method: "POST",
                            headers: {
                                "Authorization": `Bearer ${getApiKey()}`
                            },
                            body: formData
                        });
                        if (!response.ok) {
                            const errorText = await response.text();
                            console.error("Whisper API error:", errorText);
                            alert("Error with Whisper API:\n" + errorText);
                            return;
                        }
                        const result = await response.json();
                        const transcription = result.text;
                        // Find the chat input textarea and insert the transcription.
                        const chatInput = document.querySelector("textarea");
                        if (chatInput) {
                            chatInput.value = transcription;
                            // Dispatch an input event if the site relies on it.
                            chatInput.dispatchEvent(new Event('input', { bubbles: true }));
                        } else {
                            alert("Chat input not found!");
                        }
                    } catch (error) {
                        console.error("Error calling Whisper API:", error);
                        alert("Error calling Whisper API: " + error.message);
                    }
                });

                mediaRecorder.start();
            } catch (error) {
                console.error("Error accessing microphone:", error);
                alert("Error accessing microphone: " + error.message);
                sttButton.innerHTML = "🎙️ Start STT";
                isRecording = false;
            }
        } else {
            // Stop recording
            sttButton.innerHTML = "🎙️ Start STT";
            isRecording = false;
            if (mediaRecorder && mediaRecorder.state === "recording") {
                mediaRecorder.stop();
            }
        }
    });
})();