training-academy/tts-integration-test.html

358 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron TTS Integration Test</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #2c3e50;
color: white;
}
.test-section {
background: #34495e;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.enabled {
background: #27ae60;
color: white;
}
.disabled {
background: #95a5a6;
color: white;
}
#voice-info {
background: #2c3e50;
padding: 15px;
border-radius: 5px;
margin: 10px 0;
border-left: 3px solid #3498db;
}
</style>
</head>
<body>
<h1>🎙️ Electron TTS Integration Test</h1>
<div class="test-section">
<h2>Voice Manager Test</h2>
<p>This tests the cross-platform voice selection system.</p>
<button id="init-btn" onclick="initializeVoiceManager()">Initialize Voice Manager</button>
<button id="test-speak" onclick="testSpeak()" disabled>Test Female Voice</button>
<button id="stop-btn" onclick="stopSpeech()">Stop Speech</button>
<div id="voice-info">
<strong>Voice Manager Status:</strong> Not initialized
</div>
<div id="platform-info"></div>
</div>
<div class="test-section">
<h2>Scenario Text Test</h2>
<p>Test TTS with scenario-style text including placeholders.</p>
<textarea id="scenario-text" rows="4" style="width: 100%; padding: 10px; background: #2c3e50; color: white; border: 1px solid #556983; border-radius: 5px;">
You feel your {arousal} building as you continue to focus. Your {control} wavers slightly, but you maintain your composure. The {intensity} of the moment increases.
</textarea>
<div style="margin: 10px 0;">
<label>Arousal Level:
<select id="arousal-level">
<option value="low">Low</option>
<option value="moderate" selected>Moderate</option>
<option value="high">High</option>
<option value="extreme">Extreme</option>
</select>
</label>
<label style="margin-left: 20px;">Control Level:
<select id="control-level">
<option value="weak">Weak</option>
<option value="moderate" selected>Moderate</option>
<option value="strong">Strong</option>
<option value="iron">Iron</option>
</select>
</label>
<label style="margin-left: 20px;">Intensity:
<select id="intensity-level">
<option value="low">Low</option>
<option value="moderate" selected>Moderate</option>
<option value="high">High</option>
<option value="extreme">Extreme</option>
</select>
</label>
</div>
<button onclick="testScenarioSpeak()">Speak Processed Text</button>
</div>
<script type="module">
// Import the voice manager (simulated for testing)
class VoiceManager {
constructor() {
this.synth = window.speechSynthesis;
this.selectedVoice = null;
this.fallbackVoices = this.getPlatformFallbacks();
}
getPlatformFallbacks() {
const platform = this.detectPlatform();
switch (platform) {
case 'windows':
return [
'Microsoft Zira Desktop', 'Microsoft Zira',
'Microsoft Hazel Desktop', 'Microsoft Hazel',
'Microsoft Eva Desktop', 'Microsoft Eva',
'Microsoft Aria', 'Microsoft Jenny',
'Zira', 'Hazel', 'Eva', 'Aria', 'Jenny'
];
case 'mac':
return [
'Samantha', 'Victoria', 'Karen', 'Fiona', 'Moira', 'Tessa', 'Alex'
];
default:
return ['Samantha', 'Zira', 'Victoria', 'Hazel', 'Karen', 'Eva', 'female', 'woman'];
}
}
detectPlatform() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('win')) return 'windows';
if (userAgent.includes('mac')) return 'mac';
if (userAgent.includes('linux')) return 'linux';
return 'unknown';
}
isFemaleVoice(voice) {
const name = voice.name.toLowerCase();
const femaleVoiceNames = [
'zira', 'hazel', 'eva', 'aria', 'jenny',
'samantha', 'victoria', 'karen', 'fiona', 'moira', 'tessa',
'female', 'woman', 'girl', 'lady'
];
return femaleVoiceNames.some(femaleName => name.includes(femaleName));
}
getAvailableFemaleVoices() {
const voices = this.synth.getVoices();
return voices.filter(voice =>
voice.lang.startsWith('en') && this.isFemaleVoice(voice)
);
}
selectBestVoice() {
const availableVoices = this.getAvailableFemaleVoices();
if (availableVoices.length === 0) {
console.warn('No female voices available');
return null;
}
for (const preferredName of this.fallbackVoices) {
const voice = availableVoices.find(v =>
v.name.toLowerCase().includes(preferredName.toLowerCase())
);
if (voice) {
console.log(`Selected voice: ${voice.name} (${voice.lang})`);
this.selectedVoice = voice;
return voice;
}
}
this.selectedVoice = availableVoices[0];
console.log(`Fallback to first available: ${this.selectedVoice.name}`);
return this.selectedVoice;
}
async initialize() {
return new Promise((resolve) => {
if (this.synth.getVoices().length > 0) {
this.selectBestVoice();
resolve(this.selectedVoice);
} else {
this.synth.addEventListener('voiceschanged', () => {
this.selectBestVoice();
resolve(this.selectedVoice);
}, { once: true });
}
});
}
speak(text, options = {}) {
if (!this.selectedVoice) {
console.warn('No voice selected, attempting to initialize...');
this.selectBestVoice();
}
const utterance = new SpeechSynthesisUtterance(text);
if (this.selectedVoice) {
utterance.voice = this.selectedVoice;
}
utterance.rate = options.rate || 0.9;
utterance.pitch = options.pitch || 1.1;
utterance.volume = options.volume || 0.8;
// Platform-specific pitch adjustments
if (this.selectedVoice) {
const voiceName = this.selectedVoice.name.toLowerCase();
if (voiceName.includes('alex')) {
utterance.pitch = Math.max(utterance.pitch * 1.2, 1.5);
}
}
if (options.onStart) utterance.onstart = options.onStart;
if (options.onEnd) utterance.onend = options.onEnd;
if (options.onError) utterance.onerror = options.onError;
this.synth.speak(utterance);
return utterance;
}
getVoiceInfo() {
if (!this.selectedVoice) {
return {
name: 'No voice selected',
platform: this.detectPlatform(),
available: false
};
}
return {
name: this.selectedVoice.name,
lang: this.selectedVoice.lang,
platform: this.detectPlatform(),
available: true,
localService: this.selectedVoice.localService,
voiceURI: this.selectedVoice.voiceURI
};
}
stop() {
this.synth.cancel();
}
isSupported() {
return 'speechSynthesis' in window;
}
}
// Global variables
let voiceManager = null;
// Global functions
window.initializeVoiceManager = async function() {
const button = document.getElementById('init-btn');
const voiceInfo = document.getElementById('voice-info');
const platformInfo = document.getElementById('platform-info');
const testButton = document.getElementById('test-speak');
button.textContent = 'Initializing...';
button.disabled = true;
try {
voiceManager = new VoiceManager();
await voiceManager.initialize();
const info = voiceManager.getVoiceInfo();
voiceInfo.innerHTML = `
<strong>Voice Manager Status:</strong> ✅ Initialized<br>
<strong>Selected Voice:</strong> ${info.name}<br>
<strong>Language:</strong> ${info.lang}<br>
<strong>Platform:</strong> ${info.platform}<br>
<strong>Local Service:</strong> ${info.localService ? 'Yes' : 'No'}
`;
// Show available female voices
const femaleVoices = voiceManager.getAvailableFemaleVoices();
platformInfo.innerHTML = `
<h4>Available Female Voices (${femaleVoices.length}):</h4>
<ul>
${femaleVoices.map(voice =>
`<li>${voice.name} (${voice.lang}) ${voice === voiceManager.selectedVoice ? '⭐ Selected' : ''}</li>`
).join('')}
</ul>
`;
button.textContent = '✅ Voice Manager Ready';
button.className = 'enabled';
testButton.disabled = false;
} catch (error) {
voiceInfo.innerHTML = `<strong>Error:</strong> Failed to initialize voice manager - ${error.message}`;
button.textContent = '❌ Initialization Failed';
button.className = 'disabled';
}
};
window.testSpeak = function() {
if (!voiceManager) {
alert('Please initialize voice manager first');
return;
}
const testText = "Hello! I am your female voice assistant for the interactive scenarios. This voice will be used on both Windows and Mac platforms.";
voiceManager.speak(testText, {
onStart: () => console.log('🎤 TTS started'),
onEnd: () => console.log('🎤 TTS finished'),
onError: (error) => console.error('🎤 TTS error:', error)
});
};
window.stopSpeech = function() {
if (voiceManager) {
voiceManager.stop();
}
};
window.testScenarioSpeak = function() {
if (!voiceManager) {
alert('Please initialize voice manager first');
return;
}
const text = document.getElementById('scenario-text').value;
const arousal = document.getElementById('arousal-level').value;
const control = document.getElementById('control-level').value;
const intensity = document.getElementById('intensity-level').value;
// Process the text like in the game
const processedText = text
.replace(/\{arousal\}/g, arousal)
.replace(/\{control\}/g, control)
.replace(/\{intensity\}/g, intensity);
console.log('Original text:', text);
console.log('Processed text:', processedText);
voiceManager.speak(processedText, {
rate: 0.9,
pitch: 1.1,
onStart: () => console.log('🎤 Scenario TTS started'),
onEnd: () => console.log('🎤 Scenario TTS finished')
});
};
</script>
</body>
</html>