Class_generator/src/components/SettingsDebug.js
StillHammer 8ebc0b2334 Add TTS service, deployment docs, and refactor game modules
- Add TTSService.js for text-to-speech functionality
- Add comprehensive deployment documentation (guides, checklists, diagnostics)
- Add new SBS content (chapters 8 & 9)
- Refactor 14 game modules for better maintainability (-947 lines)
- Enhance SettingsDebug.js with improved debugging capabilities
- Update configuration files and startup scripts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 23:41:12 +08:00

1022 lines
37 KiB
JavaScript

import Module from '../core/Module.js';
import ttsService from '../services/TTSService.js';
class SettingsDebug extends Module {
constructor(name, dependencies, config) {
super(name, ['eventBus', 'router']);
if (!dependencies.eventBus || !dependencies.router) {
throw new Error('SettingsDebug requires EventBus and Router dependencies');
}
this._eventBus = dependencies.eventBus;
this._router = dependencies.router;
this._config = config || {};
// Internal state
this._container = null;
this._debugMessages = [];
this._availableVoices = [];
this._ttsSettings = {
rate: 0.85,
pitch: 1.0,
volume: 1.0,
voicesByLanguage: {} // e.g., { 'en-US': 'Google US English', 'zh-CN': 'Google 普通话' }
};
Object.seal(this);
}
async init() {
this._validateNotDestroyed();
this._loadTTSSettings();
this._injectCSS();
this._setupEventListeners();
this._exposePublicAPI();
this._setInitialized();
}
async destroy() {
this._validateNotDestroyed();
if (this._container) {
this._container.innerHTML = '';
}
this._removeInjectedCSS();
this._eventBus.off('settings:show', this._handleShowSettings.bind(this), this.name);
this._setDestroyed();
}
// Public API
show(container) {
this._validateInitialized();
this._container = container;
this._render();
this._loadVoices();
this._updateBrowserInfo();
this._addDebugMessage('Settings/Debug panel opened', 'info');
}
hide() {
this._validateInitialized();
if (this._container) {
this._container.innerHTML = '';
this._container = null;
}
}
// Private methods
_setupEventListeners() {
this._eventBus.on('settings:show', this._handleShowSettings.bind(this), this.name);
this._eventBus.on('router:navigate', this._handleNavigation.bind(this), this.name);
this._eventBus.on('navigation:settings', this._handleNavigationSettings.bind(this), this.name);
}
_handleShowSettings(event) {
if (event.data && event.data.container) {
this.show(event.data.container);
}
}
_handleNavigation(event) {
if (event.data && event.data.path !== '/settings') {
this.hide();
}
}
_handleNavigationSettings(event) {
// Find the main container or create one
let container = document.getElementById('main-content');
if (!container) {
container = document.querySelector('main') || document.body;
}
this.show(container);
}
_loadTTSSettings() {
try {
// Load settings from TTSService
const defaults = ttsService.getDefaults();
this._ttsSettings.rate = defaults.rate;
this._ttsSettings.volume = defaults.volume;
this._ttsSettings.pitch = defaults.pitch;
// Also try to load from localStorage for voicesByLanguage
const saved = localStorage.getItem('tts-settings');
if (saved) {
const savedSettings = JSON.parse(saved);
if (savedSettings.voicesByLanguage) {
this._ttsSettings.voicesByLanguage = savedSettings.voicesByLanguage;
}
}
} catch (e) {
this._addDebugMessage(`Failed to load TTS settings: ${e.message}`, 'warning');
}
}
_saveTTSSettings() {
try {
// Apply settings to TTSService
ttsService.setDefaults({
rate: this._ttsSettings.rate,
volume: this._ttsSettings.volume,
pitch: this._ttsSettings.pitch
});
// Apply preferred voices to TTSService
ttsService.setPreferredVoices(this._ttsSettings.voicesByLanguage);
// Save to localStorage
localStorage.setItem('tts-settings', JSON.stringify(this._ttsSettings));
this._addDebugMessage('TTS settings saved and applied globally', 'success');
} catch (e) {
this._addDebugMessage(`Failed to save TTS settings: ${e.message}`, 'error');
}
}
_injectCSS() {
if (document.getElementById('settings-debug-styles')) return;
const styleSheet = document.createElement('style');
styleSheet.id = 'settings-debug-styles';
styleSheet.textContent = `
.settings-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
}
.settings-section {
background: var(--card-background, #fff);
border-radius: 12px;
padding: 25px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border-color, #e5e7eb);
}
.settings-section h3 {
font-size: 1.4em;
margin-bottom: 20px;
color: var(--text-primary, #111827);
display: flex;
align-items: center;
gap: 8px;
}
.setting-group {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.setting-group:last-child {
border-bottom: none;
margin-bottom: 0;
}
.setting-group label {
font-weight: 500;
color: var(--text-primary, #111827);
min-width: 140px;
}
.setting-group input[type="range"] {
flex: 1;
margin: 0 15px;
accent-color: var(--primary-color, #3b82f6);
}
.setting-group select {
min-width: 200px;
padding: 8px 12px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 6px;
font-size: 14px;
background: white;
}
.setting-group span {
min-width: 40px;
text-align: center;
font-weight: 600;
color: var(--primary-color, #3b82f6);
}
.debug-info {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #e9ecef;
}
.info-item:last-child {
border-bottom: none;
}
.info-item .label {
font-weight: 500;
color: var(--text-secondary, #6b7280);
}
.info-item .value {
font-weight: 600;
color: var(--text-primary, #111827);
}
.info-item .value.small {
font-size: 0.85em;
max-width: 400px;
word-break: break-all;
}
.debug-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.debug-btn {
padding: 12px 16px;
border: none;
border-radius: 8px;
background: var(--primary-color, #3b82f6);
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.debug-btn:hover {
background: #2563eb;
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.debug-btn:active {
transform: translateY(0);
}
.debug-output {
background: #1a1a1a;
border-radius: 8px;
padding: 15px;
color: #e0e0e0;
}
.debug-output h4 {
color: #ffffff;
margin-bottom: 10px;
font-size: 1.1em;
}
.debug-log {
min-height: 120px;
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
background: #000000;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.debug-log:empty::before {
content: "No debug output yet. Click test buttons above.";
color: #888;
font-style: italic;
}
.clear-btn {
padding: 6px 12px;
border: 1px solid #555;
border-radius: 4px;
background: #333;
color: #fff;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.clear-btn:hover {
background: #444;
border-color: #666;
}
.voice-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
max-height: 300px;
overflow-y: auto;
}
.voice-item {
background: #f8f9fa;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 8px;
padding: 12px;
transition: all 0.3s ease;
cursor: pointer;
}
.voice-item:hover {
background: #e9ecef;
border-color: var(--primary-color, #3b82f6);
}
.voice-item.selected {
background: var(--primary-light, #dbeafe);
border-color: var(--primary-color, #3b82f6);
}
.voice-name {
font-weight: 600;
color: var(--text-primary, #111827);
margin-bottom: 4px;
}
.voice-lang {
font-size: 0.9em;
color: var(--text-secondary, #6b7280);
margin-bottom: 4px;
}
.voice-type {
font-size: 0.8em;
color: var(--accent-color, #f59e0b);
font-weight: 500;
}
.debug-log .success { color: #4ade80; }
.debug-log .error { color: #f87171; }
.debug-log .warning { color: #fbbf24; }
.debug-log .info { color: #60a5fa; }
@media (max-width: 768px) {
.settings-container {
padding: 15px;
gap: 20px;
}
.setting-group {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.setting-group input[type="range"] {
width: 100%;
margin: 10px 0;
}
.debug-controls {
grid-template-columns: 1fr;
}
.voice-list {
grid-template-columns: 1fr;
}
}
`;
document.head.appendChild(styleSheet);
}
_removeInjectedCSS() {
const styleSheet = document.getElementById('settings-debug-styles');
if (styleSheet) {
styleSheet.remove();
}
}
_render() {
if (!this._container) return;
this._container.innerHTML = `
<div class="settings-container">
<!-- Navigation Button -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<button class="debug-btn" style="background: #6b7280; width: auto; padding: 10px 20px;" onclick="window.app.getCore().router.navigate('/')">
🏠 Return to Home
</button>
<h2 style="margin: 0; color: var(--text-primary, #111827);">Settings & Debug</h2>
</div>
<!-- System Information -->
<div class="settings-section">
<h3>🔧 System Information</h3>
<div class="debug-info">
<div class="info-item">
<span class="label">Application Status:</span>
<span class="value" id="app-status">Running</span>
</div>
<div class="info-item">
<span class="label">Modules Loaded:</span>
<span class="value" id="modules-count">0</span>
</div>
<div class="info-item">
<span class="label">EventBus Status:</span>
<span class="value" id="eventbus-status">Active</span>
</div>
<div class="info-item">
<span class="label">Current Route:</span>
<span class="value" id="current-route">/settings</span>
</div>
<div class="info-item">
<span class="label">Browser Support:</span>
<span class="value" id="browser-support">Checking...</span>
</div>
</div>
</div>
<!-- TTS Settings -->
<div class="settings-section">
<h3>🔊 Text-to-Speech Settings <small style="font-size: 0.7em; color: #888; font-weight: normal;">(Applied globally to all games)</small></h3>
<div class="setting-group">
<label>Speech Rate:</label>
<input type="range" id="tts-rate" min="0.1" max="2" step="0.1" value="${this._ttsSettings.rate}">
<span id="tts-rate-value">${this._ttsSettings.rate}</span>
</div>
<div class="setting-group">
<label>Pitch:</label>
<input type="range" id="tts-pitch" min="0" max="2" step="0.1" value="${this._ttsSettings.pitch}">
<span id="tts-pitch-value">${this._ttsSettings.pitch}</span>
</div>
<div class="setting-group">
<label>Volume:</label>
<input type="range" id="tts-volume" min="0" max="1" step="0.1" value="${this._ttsSettings.volume}">
<span id="tts-volume-value">${this._ttsSettings.volume}</span>
</div>
<div style="display: flex; gap: 10px; margin-top: 15px;">
<button class="debug-btn" style="flex: 1;" onclick="window.settingsDebug.testCurrentSettings()">
🔊 Test Current Settings
</button>
<button class="debug-btn" style="flex: 1; background: #6b7280;" onclick="window.settingsDebug.resetToDefaults()">
🔄 Reset to Defaults
</button>
</div>
</div>
<!-- Voice Selection by Language -->
<div class="settings-section">
<h3>🎤 Voice Selection by Language</h3>
<div id="voice-language-selectors"></div>
</div>
<!-- Voice Information -->
<div class="settings-section">
<h3>🎤 Voice Information</h3>
<div class="debug-info">
<div class="info-item">
<span class="label">Total Voices:</span>
<span class="value" id="voice-count">0</span>
</div>
<div class="info-item">
<span class="label">English Voices:</span>
<span class="value" id="english-voice-count">0</span>
</div>
</div>
<div class="voice-list" id="voice-list"></div>
</div>
<!-- Debug Controls -->
<div class="settings-section">
<h3>🧪 Debug Controls</h3>
<div class="debug-controls">
<button class="debug-btn" onclick="window.settingsDebug.testBasicTTS()">
🔊 Test Basic TTS
</button>
<button class="debug-btn" onclick="window.settingsDebug.testGameWords()">
📝 Test Game Words
</button>
<button class="debug-btn" onclick="window.settingsDebug.refreshVoices()">
🔄 Refresh Voices
</button>
<button class="debug-btn" onclick="window.settingsDebug.testSystem()">
⚙️ Test System
</button>
</div>
</div>
<!-- Debug Output -->
<div class="settings-section">
<div class="debug-output">
<h4>Debug Log</h4>
<div class="debug-log" id="debug-log"></div>
<button class="clear-btn" onclick="window.settingsDebug.clearDebugLog()">Clear Log</button>
</div>
</div>
<!-- Browser Information -->
<div class="settings-section">
<h3>🌐 Browser Information</h3>
<div class="debug-info">
<div class="info-item">
<span class="label">User Agent:</span>
<span class="value small" id="user-agent"></span>
</div>
<div class="info-item">
<span class="label">Platform:</span>
<span class="value" id="platform"></span>
</div>
<div class="info-item">
<span class="label">Language:</span>
<span class="value" id="browser-language"></span>
</div>
</div>
</div>
</div>
`;
this._setupControlListeners();
this._updateSystemInfo();
}
_setupControlListeners() {
// TTS Rate slider
const rateSlider = document.getElementById('tts-rate');
if (rateSlider) {
rateSlider.addEventListener('input', (e) => {
this._ttsSettings.rate = parseFloat(e.target.value);
document.getElementById('tts-rate-value').textContent = this._ttsSettings.rate;
this._saveTTSSettings();
});
}
// Pitch slider
const pitchSlider = document.getElementById('tts-pitch');
if (pitchSlider) {
pitchSlider.addEventListener('input', (e) => {
this._ttsSettings.pitch = parseFloat(e.target.value);
document.getElementById('tts-pitch-value').textContent = this._ttsSettings.pitch;
this._saveTTSSettings();
});
}
// Volume slider
const volumeSlider = document.getElementById('tts-volume');
if (volumeSlider) {
volumeSlider.addEventListener('input', (e) => {
this._ttsSettings.volume = parseFloat(e.target.value);
document.getElementById('tts-volume-value').textContent = this._ttsSettings.volume;
this._saveTTSSettings();
});
}
// Voice selection
const voiceSelect = document.getElementById('tts-voice');
if (voiceSelect) {
voiceSelect.addEventListener('change', (e) => {
this._ttsSettings.selectedVoice = e.target.value;
this._saveTTSSettings();
});
}
}
_updateSystemInfo() {
// Get application instance from global
if (window.app) {
const status = window.app.getStatus();
const moduleLoader = window.app.getCore()?.moduleLoader;
if (status) {
document.getElementById('app-status').textContent = status.status;
}
if (moduleLoader) {
const moduleStatus = moduleLoader.getStatus();
document.getElementById('modules-count').textContent = moduleStatus?.loaded?.length || 0;
}
}
// Current route
document.getElementById('current-route').textContent = window.location.pathname || '/';
// EventBus status
const eventBusStatus = this._eventBus ? 'Active' : 'Inactive';
document.getElementById('eventbus-status').textContent = eventBusStatus;
}
_updateBrowserInfo() {
const elements = {
'user-agent': navigator.userAgent,
'platform': navigator.platform,
'browser-language': navigator.language
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
});
this._checkBrowserSupport();
}
_checkBrowserSupport() {
const checks = [
{ name: 'speechSynthesis', available: 'speechSynthesis' in window },
{ name: 'SpeechSynthesisUtterance', available: 'SpeechSynthesisUtterance' in window },
{ name: 'getVoices', available: speechSynthesis && typeof speechSynthesis.getVoices === 'function' },
{ name: 'speak', available: speechSynthesis && typeof speechSynthesis.speak === 'function' }
];
const support = checks.every(check => check.available);
const supportElement = document.getElementById('browser-support');
if (supportElement) {
supportElement.textContent = support ? '✅ Full Support' : '❌ Limited Support';
supportElement.style.color = support ? '#22C55E' : '#EF4444';
}
this._addDebugMessage(`Browser TTS Support: ${support ? 'Full' : 'Limited'}`, support ? 'success' : 'warning');
return support;
}
async _loadVoices() {
try {
// Use TTSService to get voices
this._availableVoices = await ttsService.getVoices();
this._updateVoiceInfo();
this._populateVoiceByLanguageSelectors();
this._displayVoiceList();
} catch (error) {
this._addDebugMessage(`Failed to load voices: ${error.message}`, 'error');
}
}
_updateVoiceInfo() {
const voiceCountElement = document.getElementById('voice-count');
const englishVoiceCountElement = document.getElementById('english-voice-count');
if (voiceCountElement) {
voiceCountElement.textContent = this._availableVoices.length;
}
const englishVoices = this._availableVoices.filter(voice => voice.lang.startsWith('en'));
if (englishVoiceCountElement) {
englishVoiceCountElement.textContent = englishVoices.length;
}
}
_populateVoiceByLanguageSelectors() {
const container = document.getElementById('voice-language-selectors');
if (!container) return;
// Group voices by language prefix
const voicesByLang = {};
this._availableVoices.forEach(voice => {
const langPrefix = voice.lang.split('-')[0];
// Group by prefix (e.g., 'en' for all English variants: en-US, en-GB, etc.)
if (!voicesByLang[langPrefix]) {
voicesByLang[langPrefix] = [];
}
voicesByLang[langPrefix].push(voice);
});
// Only show these main languages
const supportedLanguages = ['en', 'zh', 'fr', 'ja'];
// Language names mapping
const langNames = {
'en': 'English',
'zh': 'Chinese (中文)',
'fr': 'French (Français)',
'ja': 'Japanese (日本語)'
};
// Create selectors for each supported language
container.innerHTML = '';
const availableLangs = supportedLanguages.filter(lang => voicesByLang[lang] && voicesByLang[lang].length > 0);
availableLangs.forEach(langPrefix => {
const voices = voicesByLang[langPrefix];
const langName = langNames[langPrefix];
const selectorGroup = document.createElement('div');
selectorGroup.className = 'setting-group';
selectorGroup.innerHTML = `
<label>${langName}:</label>
<select class="voice-lang-select" data-lang="${langPrefix}" style="flex: 1; margin: 0 15px;">
<option value="">Auto (Best Available)</option>
</select>
<button class="debug-btn" style="padding: 6px 12px; font-size: 0.85em;" onclick="window.settingsDebug.testVoiceForLanguage('${langPrefix}')">
🔊 Test
</button>
`;
const select = selectorGroup.querySelector('select');
// Populate voice options for this language
voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.name;
const quality = voice.localService ? '🟢' : '🔵';
option.textContent = `${quality} ${voice.name} (${voice.lang})`;
// Check if this voice is selected for any variant of this language
const savedVoice = this._ttsSettings.voicesByLanguage[voice.lang];
if (savedVoice === voice.name) {
option.selected = true;
}
select.appendChild(option);
});
// Add change listener
select.addEventListener('change', (e) => {
const selectedVoiceName = e.target.value;
// Store the voice for all variants of this language
voices.forEach(voice => {
if (selectedVoiceName === '') {
delete this._ttsSettings.voicesByLanguage[voice.lang];
} else {
// Set the selected voice for ALL variants of this language
this._ttsSettings.voicesByLanguage[voice.lang] = selectedVoiceName;
}
});
this._saveTTSSettings();
this._addDebugMessage(`Voice for ${langName} set to: ${selectedVoiceName || 'Auto'}`, 'success');
});
container.appendChild(selectorGroup);
});
}
_displayVoiceList() {
const voiceListElement = document.getElementById('voice-list');
if (!voiceListElement) return;
if (this._availableVoices.length === 0) {
voiceListElement.innerHTML = '<div style="text-align: center; color: #666;">No voices available</div>';
return;
}
voiceListElement.innerHTML = '';
this._availableVoices.forEach(voice => {
const voiceItem = document.createElement('div');
voiceItem.className = 'voice-item';
voiceItem.innerHTML = `
<div class="voice-name">${voice.name}</div>
<div class="voice-lang">${voice.lang}</div>
<div class="voice-type">${voice.localService ? 'Local' : 'Remote'}</div>
`;
voiceItem.addEventListener('click', () => {
this._testVoice(voice);
document.querySelectorAll('.voice-item').forEach(item => item.classList.remove('selected'));
voiceItem.classList.add('selected');
});
voiceListElement.appendChild(voiceItem);
});
}
_testVoice(voice) {
try {
const utterance = new SpeechSynthesisUtterance('Hello, this is a voice test');
utterance.voice = voice;
utterance.rate = this._ttsSettings.rate;
utterance.volume = this._ttsSettings.volume;
utterance.onstart = () => {
this._addDebugMessage(`Testing voice: ${voice.name}`, 'info');
};
utterance.onerror = (event) => {
this._addDebugMessage(`Voice test error: ${event.error}`, 'error');
};
speechSynthesis.speak(utterance);
} catch (error) {
this._addDebugMessage(`Voice test failed: ${error.message}`, 'error');
}
}
_addDebugMessage(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
this._debugMessages.push({ message: logEntry, type });
this._updateDebugDisplay();
console.log(`[Settings] ${logEntry}`);
}
_updateDebugDisplay() {
const debugLogElement = document.getElementById('debug-log');
if (!debugLogElement) return;
const lastEntries = this._debugMessages.slice(-50);
debugLogElement.innerHTML = lastEntries
.map(entry => `<span class="${entry.type}">${entry.message}</span>`)
.join('\n');
debugLogElement.scrollTop = debugLogElement.scrollHeight;
}
// Public test methods (exposed via window.settingsDebug)
testBasicTTS() {
this._addDebugMessage('Testing basic TTS...', 'info');
this._speak('Hello world, this is a basic test')
.then(() => this._addDebugMessage('✅ Basic TTS test completed', 'success'))
.catch(error => this._addDebugMessage(`❌ Basic TTS test failed: ${error.message}`, 'error'));
}
testGameWords() {
this._addDebugMessage('Testing game vocabulary words...', 'info');
const words = ['apple', 'cat', 'house', 'car', 'tree', 'book', 'sun', 'dog'];
let index = 0;
const speakNext = () => {
if (index >= words.length) {
this._addDebugMessage('✅ Game words test completed', 'success');
return;
}
const word = words[index++];
this._speak(word)
.then(() => {
this._addDebugMessage(`✅ Spoke: ${word}`, 'info');
setTimeout(speakNext, 500);
})
.catch(error => {
this._addDebugMessage(`❌ Failed to speak ${word}: ${error.message}`, 'error');
setTimeout(speakNext, 500);
});
};
speakNext();
}
refreshVoices() {
this._addDebugMessage('Refreshing voice list...', 'info');
this._loadVoices();
this._addDebugMessage('✅ Voice list refreshed', 'success');
}
testSystem() {
this._addDebugMessage('Testing system components...', 'info');
// Test EventBus
if (this._eventBus) {
this._addDebugMessage('✅ EventBus: Active', 'success');
} else {
this._addDebugMessage('❌ EventBus: Not found', 'error');
}
// Test Router
if (this._router) {
this._addDebugMessage('✅ Router: Active', 'success');
} else {
this._addDebugMessage('❌ Router: Not found', 'error');
}
// Test Application
if (window.app) {
this._addDebugMessage('✅ Application: Running', 'success');
} else {
this._addDebugMessage('❌ Application: Not found', 'error');
}
this._addDebugMessage('System test completed', 'info');
this._updateSystemInfo();
}
clearDebugLog() {
this._debugMessages = [];
this._updateDebugDisplay();
this._addDebugMessage('Debug log cleared', 'info');
}
testCurrentSettings() {
this._addDebugMessage(`Testing TTS with current settings (rate: ${this._ttsSettings.rate}, pitch: ${this._ttsSettings.pitch}, volume: ${this._ttsSettings.volume})...`, 'info');
const testText = 'Hello! This is a test of your current text-to-speech settings. You can adjust the rate, pitch, and volume to your preference.';
this._speak(testText)
.then(() => this._addDebugMessage('✅ TTS test completed successfully', 'success'))
.catch(error => this._addDebugMessage(`❌ TTS test failed: ${error.message}`, 'error'));
}
testVoiceForLanguage(langPrefix) {
this._addDebugMessage(`Testing voice for language: ${langPrefix}...`, 'info');
// Get test phrases for supported languages
const testPhrases = {
'en': 'Hello! This is a test of the English voice.',
'zh': '你好!这是中文语音的测试。',
'fr': 'Bonjour ! Ceci est un test de la voix française.',
'ja': 'こんにちは!これは日本語音声のテストです。'
};
const testPhrase = testPhrases[langPrefix] || 'Hello, this is a voice test.';
const language = langPrefix + '-' + langPrefix.toUpperCase(); // e.g., 'en-EN'
this._speak(testPhrase, { lang: language })
.then(() => this._addDebugMessage(`${langPrefix} voice test completed`, 'success'))
.catch(error => this._addDebugMessage(`${langPrefix} voice test failed: ${error.message}`, 'error'));
}
resetToDefaults() {
this._addDebugMessage('Resetting TTS settings to defaults...', 'info');
// Reset to default values
this._ttsSettings.rate = 0.85;
this._ttsSettings.pitch = 1.0;
this._ttsSettings.volume = 1.0;
this._ttsSettings.voicesByLanguage = {};
// Update UI
const rateSlider = document.getElementById('tts-rate');
const pitchSlider = document.getElementById('tts-pitch');
const volumeSlider = document.getElementById('tts-volume');
if (rateSlider) {
rateSlider.value = this._ttsSettings.rate;
document.getElementById('tts-rate-value').textContent = this._ttsSettings.rate;
}
if (pitchSlider) {
pitchSlider.value = this._ttsSettings.pitch;
document.getElementById('tts-pitch-value').textContent = this._ttsSettings.pitch;
}
if (volumeSlider) {
volumeSlider.value = this._ttsSettings.volume;
document.getElementById('tts-volume-value').textContent = this._ttsSettings.volume;
}
// Reset all language voice selectors
document.querySelectorAll('.voice-lang-select').forEach(select => {
select.value = '';
});
// Save settings
this._saveTTSSettings();
this._addDebugMessage('✅ Settings reset to defaults (rate: 0.85, pitch: 1.0, volume: 1.0, all voices: Auto)', 'success');
}
_exposePublicAPI() {
// Expose API for debug buttons to use
window.settingsDebug = {
testBasicTTS: () => this.testBasicTTS(),
testGameWords: () => this.testGameWords(),
refreshVoices: () => this.refreshVoices(),
testSystem: () => this.testSystem(),
clearDebugLog: () => this.clearDebugLog(),
testCurrentSettings: () => this.testCurrentSettings(),
resetToDefaults: () => this.resetToDefaults(),
testVoiceForLanguage: (langPrefix) => this.testVoiceForLanguage(langPrefix)
};
}
async _speak(text, options = {}) {
try {
const language = options.lang || 'en-US';
const ttsOptions = {
rate: options.rate || this._ttsSettings.rate,
volume: options.volume || this._ttsSettings.volume,
pitch: this._ttsSettings.pitch
};
await ttsService.speak(text, language, ttsOptions);
} catch (error) {
throw error;
}
}
}
export default SettingsDebug;