ChatGTP answer alert

Notify when ChatGPT is done answering.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ChatGTP answer alert
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Notify when ChatGPT is done answering.
// @author       Jonathan Lepage
// @match        https://chatgpt.com/c/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    console.log("Answer detection script")
    let thinking = false
    setInterval(() => {
        //console.log("Checking for answer. hidden=", document.hidden, " hasFocus=", document.hasFocus())
        if (document.querySelector('button[aria-label="Stop streaming"]')) {
            if (!thinking) {
                thinking = true
                console.log("Thinking")
            }
        } else {
            if (thinking) {
                thinking = false
                console.log("Answer is ready")
                if (document.hidden || !document.hasFocus()) {
                    console.log("Tab is not focused: Showing notification")
                    const notif = new Notification("Answer is ready")
                    notif.onclick = () => {
                        window.focus();
                        notif.close();
                    };
                    chime()
                } else {
                    console.log("Tab is focused: Skipping notification")
                }
            }
        }
    }, 100)

    function chime() {
        const context = new AudioContext()
        const gainNode = context.createGain()
        gainNode.connect(context.destination)
        gainNode.gain.value = 0.2

        const biquadFilter = context.createBiquadFilter()
        biquadFilter.type = "lowpass"
        biquadFilter.frequency.value = 2000
        biquadFilter.connect(gainNode)

        const notes = [0, 12, 24]

        let startTime = context.currentTime
        for (const [index, note] of notes.entries()) {
            const freq = 440 * Math.pow(2, note / 12)
            const duration = 0.06

            const oscillator = context.createOscillator()
            oscillator.connect(biquadFilter)
            oscillator.type = "sawtooth"
            oscillator.frequency.value = freq
            oscillator.start(startTime)
            oscillator.stop(startTime + duration)
            startTime += duration
        }
    }
})();