// environment.service.ts // Path: /flare-ui/src/app/services/environment.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { Environment, ProviderSettings } from './api.service'; export interface EnvironmentError { type: 'validation' | 'update' | 'unknown'; message: string; details?: any; } @Injectable({ providedIn: 'root' }) export class EnvironmentService { private environmentSubject = new BehaviorSubject(null); public environment$ = this.environmentSubject.asObservable(); private ttsEnabledSource = new BehaviorSubject(false); private sttEnabledSource = new BehaviorSubject(false); private errorSubject = new BehaviorSubject(null); public error$ = this.errorSubject.asObservable(); // Local storage keys private readonly TTS_KEY = 'flare_tts_enabled'; private readonly STT_KEY = 'flare_stt_enabled'; ttsEnabled$ = this.ttsEnabledSource.asObservable(); sttEnabled$ = this.sttEnabledSource.asObservable(); constructor() { this.loadPreferences(); } private loadPreferences(): void { try { const savedTTS = localStorage.getItem(this.TTS_KEY); if (savedTTS !== null) { this.ttsEnabledSource.next(savedTTS === 'true'); } const savedSTT = localStorage.getItem(this.STT_KEY); if (savedSTT !== null) { this.sttEnabledSource.next(savedSTT === 'true'); } } catch (error) { console.error('Error loading preferences:', error); this.ttsEnabledSource.next(false); this.sttEnabledSource.next(false); } } setTTSEnabled(enabled: boolean): void { try { if (typeof enabled !== 'boolean') { throw new Error('TTS enabled must be a boolean value'); } this.ttsEnabledSource.next(enabled); try { localStorage.setItem(this.TTS_KEY, enabled.toString()); } catch (error) { console.warn('Failed to save TTS preference:', error); } console.log(`TTS ${enabled ? 'enabled' : 'disabled'}`); } catch (error) { this.handleError('validation', 'Invalid TTS setting', error); } } setSTTEnabled(enabled: boolean): void { try { if (typeof enabled !== 'boolean') { throw new Error('STT enabled must be a boolean value'); } this.sttEnabledSource.next(enabled); try { localStorage.setItem(this.STT_KEY, enabled.toString()); } catch (error) { console.warn('Failed to save STT preference:', error); } console.log(`STT ${enabled ? 'enabled' : 'disabled'}`); } catch (error) { this.handleError('validation', 'Invalid STT setting', error); } } isTTSEnabled(): boolean { return this.ttsEnabledSource.value; } isSTTEnabled(): boolean { return this.sttEnabledSource.value; } updateEnvironment(env: Environment | null): void { try { this.environmentSubject.next(env); this.errorSubject.next(null); if (env) { console.log('Environment updated:', { llm_provider: env.llm_provider.name, tts_provider: env.tts_provider.name, stt_provider: env.stt_provider.name }); // Update TTS/STT enabled states based on provider if (env.tts_provider.name !== 'no_tts') { this.setTTSEnabled(true); } if (env.stt_provider.name !== 'no_stt') { this.setSTTEnabled(true); } } } catch (error: any) { this.handleError('update', error.message || 'Failed to update environment', error); } } getEnvironment(): Environment | null { return this.environmentSubject.value; } getCurrentLLMProvider(): ProviderSettings | null { const env = this.environmentSubject.value; return env?.llm_provider || null; } getCurrentTTSProvider(): ProviderSettings | null { const env = this.environmentSubject.value; return env?.tts_provider || null; } getCurrentSTTProvider(): ProviderSettings | null { const env = this.environmentSubject.value; return env?.stt_provider || null; } isGPTMode(): boolean { try { const env = this.environmentSubject.value; const llmName = env?.llm_provider?.name?.toLowerCase(); return llmName?.startsWith('gpt4o') || false; } catch (error) { console.error('Error checking GPT mode:', error); return false; } } getWorkMode(): string | null { const env = this.environmentSubject.value; return env?.llm_provider?.name || null; } isTTSAvailable(): boolean { const env = this.environmentSubject.value; return env?.tts_provider?.name !== 'no_tts' && env?.tts_provider?.name !== undefined; } isSTTAvailable(): boolean { const env = this.environmentSubject.value; return env?.stt_provider?.name !== 'no_stt' && env?.stt_provider?.name !== undefined; } // Check if environment supports a specific feature supportsFeature(feature: 'tts' | 'stt' | 'realtime' | 'streaming'): boolean { const env = this.environmentSubject.value; if (!env) return false; switch (feature) { case 'tts': return this.isTTSAvailable() && this.isTTSEnabled(); case 'stt': return this.isSTTAvailable() && this.isSTTEnabled(); case 'realtime': return this.isGPTMode(); case 'streaming': return true; default: return false; } } // Get available providers getAvailableProviders(type: 'llm' | 'tts' | 'stt'): any[] { const env = this.environmentSubject.value; if (!env?.providers) return []; return env.providers.filter(p => p.type === type); } // Reset all settings reset(): void { try { this.environmentSubject.next(null); this.setTTSEnabled(false); this.setSTTEnabled(false); this.errorSubject.next(null); try { localStorage.removeItem(this.TTS_KEY); localStorage.removeItem(this.STT_KEY); } catch (error) { console.warn('Failed to clear preferences:', error); } console.log('Environment service reset'); } catch (error) { this.handleError('unknown', 'Failed to reset environment', error); } } // Get configuration summary getConfigSummary(): { llmProvider: string | null; ttsProvider: string | null; sttProvider: string | null; ttsEnabled: boolean; sttEnabled: boolean; features: string[]; } { const env = this.environmentSubject.value; const features: string[] = []; if (this.supportsFeature('tts')) features.push('TTS'); if (this.supportsFeature('stt')) features.push('STT'); if (this.supportsFeature('realtime')) features.push('Realtime'); if (this.supportsFeature('streaming')) features.push('Streaming'); return { llmProvider: env?.llm_provider?.name || null, ttsProvider: env?.tts_provider?.name || null, sttProvider: env?.stt_provider?.name || null, ttsEnabled: this.isTTSEnabled(), sttEnabled: this.isSTTEnabled(), features }; } private handleError(type: EnvironmentError['type'], message: string, details?: any): void { const error: EnvironmentError = { type, message, details }; console.error(`Environment error [${type}]:`, message, details); this.errorSubject.next(error); } // Observable to check if environment is ready isReady(): Observable { return new Observable(subscriber => { const sub = this.environment$.subscribe(env => { // Environment is ready if we have all providers configured const isReady = !!( env && env.llm_provider && env.tts_provider && env.stt_provider ); subscriber.next(isReady); }); return () => sub.unsubscribe(); }); } // Get error state hasError(): boolean { return this.errorSubject.value !== null; } clearError(): void { this.errorSubject.next(null); } }