Spaces:
Runtime error
Runtime error
export class VoiceSelector { | |
constructor(voiceService) { | |
this.voiceService = voiceService; | |
this.elements = { | |
voiceSearch: document.getElementById('voice-search'), | |
voiceDropdown: document.getElementById('voice-dropdown'), | |
voiceOptions: document.getElementById('voice-options'), | |
selectedVoices: document.getElementById('selected-voices') | |
}; | |
this.setupEventListeners(); | |
} | |
setupEventListeners() { | |
// Voice search focus | |
this.elements.voiceSearch.addEventListener('focus', () => { | |
this.elements.voiceDropdown.classList.add('show'); | |
}); | |
// Voice search | |
this.elements.voiceSearch.addEventListener('input', (e) => { | |
const filteredVoices = this.voiceService.filterVoices(e.target.value); | |
this.renderVoiceOptions(filteredVoices); | |
}); | |
// Voice selection - handle clicks on the entire voice option | |
this.elements.voiceOptions.addEventListener('mousedown', (e) => { | |
e.preventDefault(); // Prevent blur on search input | |
const voiceOption = e.target.closest('.voice-option'); | |
if (!voiceOption) return; | |
const voice = voiceOption.dataset.voice; | |
if (!voice) return; | |
const isSelected = voiceOption.classList.contains('selected'); | |
if (!isSelected) { | |
this.voiceService.addVoice(voice); | |
} else { | |
this.voiceService.removeVoice(voice); | |
} | |
voiceOption.classList.toggle('selected'); | |
this.updateSelectedVoicesDisplay(); | |
// Keep focus on search input | |
requestAnimationFrame(() => { | |
this.elements.voiceSearch.focus(); | |
}); | |
}); | |
// Weight adjustment | |
this.elements.selectedVoices.addEventListener('input', (e) => { | |
if (e.target.type === 'number') { | |
const voice = e.target.dataset.voice; | |
let weight = parseFloat(e.target.value); | |
// Ensure weight is between 0.1 and 10 | |
weight = Math.max(0.1, Math.min(10, weight)); | |
e.target.value = weight; | |
this.voiceService.updateWeight(voice, weight); | |
} | |
}); | |
// Remove selected voice | |
this.elements.selectedVoices.addEventListener('click', (e) => { | |
if (e.target.classList.contains('remove-voice')) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
const voice = e.target.dataset.voice; | |
this.voiceService.removeVoice(voice); | |
this.updateVoiceOptionState(voice, false); | |
this.updateSelectedVoicesDisplay(); | |
} | |
}); | |
// Handle clicks outside to close dropdown | |
document.addEventListener('mousedown', (e) => { | |
// Don't handle clicks in selected voices area | |
if (this.elements.selectedVoices.contains(e.target)) { | |
return; | |
} | |
// Don't close if clicking in search or dropdown | |
if (this.elements.voiceSearch.contains(e.target) || | |
this.elements.voiceDropdown.contains(e.target)) { | |
return; | |
} | |
this.elements.voiceDropdown.classList.remove('show'); | |
this.elements.voiceSearch.blur(); | |
}); | |
this.elements.voiceSearch.addEventListener('blur', () => { | |
if (!this.elements.voiceSearch.value) { | |
this.updateSearchPlaceholder(); | |
} | |
}); | |
} | |
renderVoiceOptions(voices) { | |
this.elements.voiceOptions.innerHTML = voices | |
.map(voice => ` | |
<div class="voice-option ${this.voiceService.getSelectedVoices().includes(voice) ? 'selected' : ''}" | |
data-voice="${voice}"> | |
${voice} | |
</div> | |
`) | |
.join(''); | |
} | |
updateSelectedVoicesDisplay() { | |
const selectedVoices = this.voiceService.getSelectedVoiceWeights(); | |
this.elements.selectedVoices.innerHTML = selectedVoices | |
.map(({voice, weight}) => ` | |
<span class="selected-voice-tag"> | |
<span class="voice-name">${voice}</span> | |
<span class="voice-weight"> | |
<input type="number" | |
value="${weight}" | |
min="0.1" | |
max="10" | |
step="0.1" | |
data-voice="${voice}" | |
class="weight-input" | |
title="Voice weight (0.1 to 10)"> | |
</span> | |
<span class="remove-voice" data-voice="${voice}" title="Remove voice">×</span> | |
</span> | |
`) | |
.join(''); | |
this.updateSearchPlaceholder(); | |
} | |
updateSearchPlaceholder() { | |
const hasSelected = this.voiceService.hasSelectedVoices(); | |
this.elements.voiceSearch.placeholder = hasSelected ? | |
'Search voices...' : | |
'Search and select voices...'; | |
} | |
updateVoiceOptionState(voice, selected) { | |
const voiceOption = this.elements.voiceOptions | |
.querySelector(`[data-voice="${voice}"]`); | |
if (voiceOption) { | |
voiceOption.classList.toggle('selected', selected); | |
} | |
} | |
async initialize() { | |
try { | |
await this.voiceService.loadVoices(); | |
this.renderVoiceOptions(this.voiceService.getAvailableVoices()); | |
this.updateSelectedVoicesDisplay(); | |
return true; | |
} catch (error) { | |
console.error('Failed to initialize voice selector:', error); | |
return false; | |
} | |
} | |
} | |
export default VoiceSelector; |