Greasy Fork is available in English.
Play a sound when ChatGPT finishes generating a response
// ==UserScript==
// @name ChatGPT Done Notification
// @namespace https://github.com/yourname/chatgpt-done-notification
// @version 1.0.0
// @description Play a sound when ChatGPT finishes generating a response
// @match https://chatgpt.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const POLL_MS = 200;
function getBtn() {
return document.querySelector('#composer-submit-button');
}
function isStop(btn) {
if (!btn) return false;
const aria = (btn.getAttribute('aria-label') || '').toLowerCase();
const testid = (btn.getAttribute('data-testid') || '').toLowerCase();
return testid === 'stop-button' || aria.includes('stop');
}
let lastIsStop = null;
let hadStopPhase = false;
// 🔊 本地生成提示音(不依赖任何资源)
function playDoneSound() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const now = ctx.currentTime;
function beep(freq, start, duration) {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.0001, start);
gain.gain.exponentialRampToValueAtTime(0.15, start + 0.01);
gain.gain.exponentialRampToValueAtTime(0.0001, start + duration);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(start);
osc.stop(start + duration);
}
// 高 → 低,完成感
beep(880, now + 0.00, 0.18);
beep(660, now + 0.22, 0.22);
} catch (_) {
// 静默失败,不打扰用户
}
}
function check() {
const btn = getBtn();
// button 被销毁(生成结束常见路径)
if (!btn) {
if (hadStopPhase && lastIsStop === true) {
playDoneSound();
hadStopPhase = false;
lastIsStop = null;
}
return;
}
const nowIsStop = isStop(btn);
if (lastIsStop === null) {
lastIsStop = nowIsStop;
return;
}
if (nowIsStop) {
hadStopPhase = true;
}
// stop → send
if (hadStopPhase && lastIsStop && !nowIsStop) {
playDoneSound();
hadStopPhase = false;
}
lastIsStop = nowIsStop;
}
// 监听 DOM 变化(应对按钮重建)
const domObserver = new MutationObserver(check);
domObserver.observe(document.body, {
childList: true,
subtree: true
});
// 轮询兜底
setInterval(check, POLL_MS);
})();