feat: Implement Performance Monitor, Settings Manager, UI Manager, Voice Manager, and Utility Functions

- Added PerformanceMonitor class for tracking application performance metrics including FPS, memory usage, and response times.
- Introduced SettingsManager class to handle application settings, configuration, and persistence with local storage.
- Created UIManager class to manage UI state, updates, and user interactions, including theme toggling and loading indicators.
- Developed VoiceManager class for voice recognition and speech synthesis functionalities, including voice input and output controls.
- Added utility functions for common operations such as debouncing, throttling, UUID generation, data formatting, and local storage management.
This commit is contained in:
Krikorios
2025-05-25 04:07:31 +03:00
parent 2cb54d6cad
commit 64cb55722b
19 changed files with 4986 additions and 0 deletions
+269
View File
@@ -0,0 +1,269 @@
/**
* Voice Manager Module
* Handles voice recognition and speech synthesis functionality
*/
export default class VoiceManager {
constructor(app) {
this.app = app;
this.recognition = null;
this.synthesis = null;
this.isListening = false;
this.isSpeaking = false;
this.voices = [];
// Voice settings
this.settings = {
language: 'en-US',
continuous: false,
interimResults: false,
maxAlternatives: 1,
pitch: 1,
rate: 0.9,
volume: 0.8,
voiceName: null // Will be set during initialization
};
}
async initialize() {
await this.initializeRecognition();
await this.initializeSynthesis();
this.setupEventListeners();
}
async initializeRecognition() {
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
console.warn('Speech recognition not supported');
return;
}
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
this.recognition = new SpeechRecognition();
// Configure recognition
this.recognition.lang = this.settings.language;
this.recognition.continuous = this.settings.continuous;
this.recognition.interimResults = this.settings.interimResults;
this.recognition.maxAlternatives = this.settings.maxAlternatives;
// Setup recognition event handlers
this.recognition.onstart = () => this.handleRecognitionStart();
this.recognition.onend = () => this.handleRecognitionEnd();
this.recognition.onresult = (event) => this.handleRecognitionResult(event);
this.recognition.onerror = (event) => this.handleRecognitionError(event);
}
async initializeSynthesis() {
if (!('speechSynthesis' in window)) {
console.warn('Speech synthesis not supported');
return;
}
this.synthesis = window.speechSynthesis;
// Wait for voices to be loaded
if (this.synthesis.getVoices().length === 0) {
await new Promise(resolve => {
this.synthesis.addEventListener('voiceschanged', () => {
this.voices = this.synthesis.getVoices();
resolve();
}, { once: true });
});
} else {
this.voices = this.synthesis.getVoices();
}
// Set default voice (prefer en-US)
this.settings.voiceName = this.selectDefaultVoice();
}
setupEventListeners() {
// Voice toggle button
const voiceToggle = document.getElementById('voiceToggle');
if (voiceToggle) {
voiceToggle.addEventListener('click', () => this.toggleVoiceMode());
}
// Mic button
const micBtn = document.getElementById('micBtn');
if (micBtn) {
micBtn.addEventListener('click', () => this.startVoiceInput());
}
}
startVoiceInput() {
if (!this.recognition) {
this.app.notifications.show('❌ Voice recognition not supported', 'error');
return;
}
if (this.isListening) {
this.stopVoiceInput();
return;
}
try {
this.recognition.start();
this.updateMicButton(true);
this.app.notifications.show('🎤 Listening... Speak now!');
} catch (error) {
console.error('Failed to start voice recognition:', error);
this.app.notifications.show('❌ Failed to start voice input', 'error');
}
}
stopVoiceInput() {
if (this.recognition && this.isListening) {
this.recognition.stop();
this.updateMicButton(false);
}
}
handleRecognitionStart() {
this.isListening = true;
this.updateMicButton(true);
}
handleRecognitionEnd() {
this.isListening = false;
this.updateMicButton(false);
}
handleRecognitionResult(event) {
const transcript = event.results[0][0].transcript;
this.app.messageInput.value = transcript;
this.app.messageInput.focus();
this.app.ui.adjustTextareaHeight(this.app.messageInput);
// Auto-send if confidence is high
if (event.results[0][0].confidence > 0.8) {
this.app.messages.sendMessage();
}
}
handleRecognitionError(event) {
console.error('Speech recognition error:', event.error);
this.app.notifications.show('❌ Voice recognition error: ' + event.error, 'error');
this.updateMicButton(false);
}
updateMicButton(isActive) {
const micBtn = document.getElementById('micBtn');
if (micBtn) {
micBtn.style.color = isActive ? 'var(--error-color)' : '';
micBtn.innerHTML = isActive ? '🎙️' : '🎤';
micBtn.classList.toggle('active', isActive);
}
}
speakText(text, options = {}) {
if (!this.synthesis) {
console.warn('Speech synthesis not supported');
return;
}
// Cancel any ongoing speech
this.synthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
// Apply settings and custom options
utterance.voice = this.getVoice(options.voiceName || this.settings.voiceName);
utterance.pitch = options.pitch || this.settings.pitch;
utterance.rate = options.rate || this.settings.rate;
utterance.volume = options.volume || this.settings.volume;
// Handle events
utterance.onstart = () => {
this.isSpeaking = true;
this.app.notifications.show('🔊 Speaking...', 'info');
};
utterance.onend = () => {
this.isSpeaking = false;
};
utterance.onerror = (event) => {
console.error('Speech synthesis error:', event);
this.app.notifications.show('❌ Speech synthesis error', 'error');
this.isSpeaking = false;
};
this.synthesis.speak(utterance);
}
stopSpeaking() {
if (this.synthesis && this.isSpeaking) {
this.synthesis.cancel();
this.isSpeaking = false;
}
}
toggleVoiceMode() {
this.app.setState('voiceMode', !this.app.getState('voiceMode'));
const btn = document.getElementById('voiceToggle');
if (this.app.getState('voiceMode')) {
btn.innerHTML = '🔊 Voice ON';
btn.style.background = 'var(--success-color)';
btn.style.color = 'white';
this.app.notifications.show('🎤 Voice mode enabled');
} else {
btn.innerHTML = '🎤 Voice';
btn.style.background = '';
btn.style.color = '';
this.app.notifications.show('🔇 Voice mode disabled');
this.stopSpeaking();
}
}
getVoice(name) {
return this.voices.find(voice => voice.name === name) ||
this.voices.find(voice => voice.lang.startsWith('en')) ||
this.voices[0];
}
selectDefaultVoice() {
// Prefer English voices, with priority for specific ones
const preferredVoices = [
'Google US English',
'Alex',
'Samantha',
'Microsoft David'
];
for (const name of preferredVoices) {
const voice = this.voices.find(v => v.name === name);
if (voice) return voice.name;
}
// Fall back to any English voice
const englishVoice = this.voices.find(voice => voice.lang.startsWith('en'));
return englishVoice ? englishVoice.name : this.voices[0]?.name;
}
speakMessage(button) {
if (!this.synthesis) return;
const message = button.closest('.message').querySelector('div').textContent;
this.speakText(message);
}
isVoiceSupported() {
return ('webkitSpeechRecognition' in window) ||
('SpeechRecognition' in window) ||
('speechSynthesis' in window);
}
setVoiceSettings(settings) {
Object.assign(this.settings, settings);
if (this.recognition) {
this.recognition.lang = this.settings.language;
this.recognition.continuous = this.settings.continuous;
this.recognition.interimResults = this.settings.interimResults;
this.recognition.maxAlternatives = this.settings.maxAlternatives;
}
}
}