const NOTIFICATION_SOUND_ENABLED_STORAGE_KEY = 'notificationSoundEnabled'; const AudioContextConstructor = typeof window !== 'undefined' ? window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext : undefined; let audioContext: AudioContext | null = null; export const isNotificationSoundEnabled = (): boolean => { if (typeof localStorage === 'undefined') { return true; } return localStorage.getItem(NOTIFICATION_SOUND_ENABLED_STORAGE_KEY) !== 'false'; }; export const setNotificationSoundEnabled = (enabled: boolean): void => { if (typeof localStorage === 'undefined') { return; } localStorage.setItem(NOTIFICATION_SOUND_ENABLED_STORAGE_KEY, String(enabled)); }; const getAudioContext = (): AudioContext | null => { if (!AudioContextConstructor) { return null; } if (!audioContext) { audioContext = new AudioContextConstructor(); } return audioContext; }; const playTone = ( context: AudioContext, frequency: number, startsAt: number, duration: number, peakVolume: number, ): void => { const oscillator = context.createOscillator(); const gain = context.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(frequency, startsAt); // Shape the volume so the synthesized tone starts and stops cleanly. gain.gain.setValueAtTime(0.0001, startsAt); gain.gain.exponentialRampToValueAtTime(peakVolume, startsAt + 0.015); gain.gain.exponentialRampToValueAtTime(0.0001, startsAt + duration); oscillator.connect(gain); gain.connect(context.destination); oscillator.start(startsAt); oscillator.stop(startsAt + duration + 0.02); }; export const playChatCompletionSound = async ({ force = false } = {}): Promise => { if (!force && !isNotificationSoundEnabled()) { return; } const context = getAudioContext(); if (!context) { return; } try { if (context.state === 'suspended') { await context.resume(); } const now = context.currentTime; playTone(context, 740, now, 0.12, 0.075); playTone(context, 988, now + 0.11, 0.16, 0.06); } catch (error) { // Browsers may block audio until the page receives a user gesture. console.warn('Unable to play notification sound:', error); } };