v15.1: CRITICAL FIX - TTS Preview + Browser Whisper actually working now
Some checks failed
Build TWA APK / build-apk (push) Has been cancelled
Build & Push Docker Image / build (push) Has been cancelled

ROOT CAUSES FOUND AND FIXED:
1. TTS Preview not working: voicePreferences.js listening for wrong event
   - Was: 'tab-loaded' (never dispatched)
   - Now: 'tabChanged' (correct event name used by app.js)
   - Added immediate init if page already loaded
   - Added 500ms delay for DOM readiness

2. Browser Whisper CDN blocked: CSP too restrictive
   - Added 'unsafe-eval' to scriptSrc (required by transformers.js)
   - Added cdn.jsdelivr.net to connectSrc (worker importScripts)
   - Added childSrc directive for worker script loading
   - Better error messages in worker

3. Worker loading errors: Now logged with specific reasons
   - importScripts wrapped in try-catch
   - Posts error message to main thread
   - Verifies transformers object exists after load

Testing:
- TTS Preview should now work when clicking Settings tab
- Browser Whisper should load from CDN (or show specific error)
- Console logs will show exact init sequence
This commit is contained in:
ifedan-ed 2026-03-31 16:00:10 +00:00
parent ee3729eb57
commit 88036a45c4
3 changed files with 45 additions and 9 deletions

View file

@ -5,24 +5,37 @@
(function() {
var _inited = false;
window.addEventListener('tab-loaded', function(e) {
// Listen for tab changes (correct event name is 'tabChanged' not 'tab-loaded')
document.addEventListener('tabChanged', function(e) {
if (e.detail.tab !== 'settings' || _inited) return;
_inited = true;
console.log('[VoicePrefs] Initializing...');
initVoicePreferences();
console.log('[VoicePrefs] Initializing on settings tab load...');
// Small delay to ensure DOM is ready
setTimeout(function() {
initVoicePreferences();
}, 100);
});
// Also init on DOMContentLoaded as backup
// Also init on DOMContentLoaded as backup for direct page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
if (!_inited && document.getElementById('btn-preview-voice')) {
console.log('[VoicePrefs] Init via DOMContentLoaded');
console.log('[VoicePrefs] Init via DOMContentLoaded (direct settings page load)');
_inited = true;
initVoicePreferences();
}
}, 1000);
}, 500);
});
} else {
// Page already loaded, init immediately if on settings
setTimeout(function() {
if (!_inited && document.getElementById('btn-preview-voice')) {
console.log('[VoicePrefs] Init immediate (page already loaded)');
_inited = true;
initVoicePreferences();
}
}, 500);
}
function initVoicePreferences() {

View file

@ -3,11 +3,29 @@
// Audio never leaves the device. Model cached in IndexedDB.
// ============================================================
importScripts('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2');
console.log('[WhisperWorker] Starting worker initialization');
console.log('[WhisperWorker] Attempting to load transformers from CDN...');
try {
importScripts('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2');
console.log('[WhisperWorker] Transformers library loaded successfully');
} catch (err) {
console.error('[WhisperWorker] Failed to load transformers library:', err);
self.postMessage({
type: 'error',
message: 'Failed to load transformers library. Check network/firewall: ' + err.message
});
throw err;
}
var T = self.transformers || transformers;
if (!T) {
console.error('[WhisperWorker] Transformers object not found after import');
throw new Error('Transformers library did not initialize');
}
T.env.allowLocalModels = false;
T.env.useBrowserCache = true;
console.log('[WhisperWorker] Transformers configured');
var _pipe = null;
var _loadedModel = null;

View file

@ -21,7 +21,8 @@ app.use(helmet({
defaultSrc: ["'self'"],
// 'wasm-unsafe-eval' required for WebAssembly (Whisper in-browser transcription)
// cdn.jsdelivr.net required for @xenova/transformers worker script
scriptSrc: ["'self'", "'wasm-unsafe-eval'", 'https://cdn.jsdelivr.net'],
// 'unsafe-eval' needed for transformers.js dynamic imports in worker
scriptSrc: ["'self'", "'wasm-unsafe-eval'", "'unsafe-eval'", 'https://cdn.jsdelivr.net'],
scriptSrcAttr: ["'none'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
@ -29,8 +30,12 @@ app.use(helmet({
mediaSrc: ["'self'", 'blob:'],
connectSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.googleapis.com', 'https://fonts.gstatic.com', 'https://www.google.com', 'wss://www.google.com', 'https://clinicaltables.nlm.nih.gov',
// HuggingFace CDN for Whisper model downloads
'https://huggingface.co', 'https://cdn-lfs.huggingface.co', 'https://cdn-lfs-us-1.huggingface.co', 'https://cdn-lfs-us-2.huggingface.co'],
'https://huggingface.co', 'https://cdn-lfs.huggingface.co', 'https://cdn-lfs-us-1.huggingface.co', 'https://cdn-lfs-us-2.huggingface.co',
// jsdelivr for worker importScripts
'https://cdn.jsdelivr.net'],
workerSrc: ["'self'", 'blob:'],
// Allow workers to load scripts from jsdelivr
childSrc: ["'self'", 'blob:', 'https://cdn.jsdelivr.net'],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
}