Spaces:
Paused
Paused
| import { Component, OnInit, OnDestroy } from '@angular/core'; | |
| import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; | |
| import { FormsModule } from '@angular/forms'; | |
| import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; | |
| import { ApiService } from '../../services/api.service'; | |
| import { EnvironmentService } from '../../services/environment.service'; | |
| import { CommonModule } from '@angular/common'; | |
| import { MatCardModule } from '@angular/material/card'; | |
| import { MatFormFieldModule } from '@angular/material/form-field'; | |
| import { MatInputModule } from '@angular/material/input'; | |
| import { MatSelectModule } from '@angular/material/select'; | |
| import { MatButtonModule } from '@angular/material/button'; | |
| import { MatIconModule } from '@angular/material/icon'; | |
| import { MatSliderModule } from '@angular/material/slider'; | |
| import { MatSlideToggleModule } from '@angular/material/slide-toggle'; | |
| import { MatExpansionModule } from '@angular/material/expansion'; | |
| import { MatDividerModule } from '@angular/material/divider'; | |
| import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | |
| import { MatTooltipModule } from '@angular/material/tooltip'; | |
| import { MatDialogModule } from '@angular/material/dialog'; | |
| import { Subject, takeUntil } from 'rxjs'; | |
| // Provider interfaces | |
| interface ProviderConfig { | |
| type: string; | |
| name: string; | |
| display_name: string; | |
| requires_endpoint: boolean; | |
| requires_api_key: boolean; | |
| requires_repo_info: boolean; | |
| description?: string; | |
| } | |
| interface ProviderSettings { | |
| name: string; | |
| api_key?: string; | |
| endpoint?: string; | |
| settings: any; | |
| } | |
| interface EnvironmentConfig { | |
| llm_provider: ProviderSettings; | |
| tts_provider: ProviderSettings; | |
| stt_provider: ProviderSettings; | |
| providers: ProviderConfig[]; | |
| } | |
| ({ | |
| selector: 'app-environment', | |
| standalone: true, | |
| imports: [ | |
| CommonModule, | |
| ReactiveFormsModule, | |
| FormsModule, | |
| MatCardModule, | |
| MatFormFieldModule, | |
| MatInputModule, | |
| MatSelectModule, | |
| MatButtonModule, | |
| MatIconModule, | |
| MatSliderModule, | |
| MatSlideToggleModule, | |
| MatExpansionModule, | |
| MatDividerModule, | |
| MatProgressSpinnerModule, | |
| MatSnackBarModule, | |
| MatTooltipModule, | |
| MatDialogModule | |
| ], | |
| templateUrl: './environment.component.html', | |
| styleUrls: ['./environment.component.scss'] | |
| }) | |
| export class EnvironmentComponent implements OnInit, OnDestroy { | |
| form: FormGroup; | |
| loading = false; | |
| saving = false; | |
| isLoading = false; | |
| // Provider lists | |
| llmProviders: ProviderConfig[] = []; | |
| ttsProviders: ProviderConfig[] = []; | |
| sttProviders: ProviderConfig[] = []; | |
| // Current provider configurations | |
| currentLLMProvider?: ProviderConfig; | |
| currentTTSProvider?: ProviderConfig; | |
| currentSTTProvider?: ProviderConfig; | |
| // Settings for LLM | |
| internalPrompt: string = ''; | |
| parameterCollectionConfig: any = { | |
| enabled: false, | |
| max_params_per_question: 1, | |
| show_all_required: false, | |
| ask_optional_params: false, | |
| group_related_params: false, | |
| min_confidence_score: 0.7, | |
| collection_prompt: 'Please provide the following information:' | |
| }; | |
| hideSTTKey = true; | |
| sttLanguages = [ | |
| { code: 'tr-TR', name: 'Türkçe' }, | |
| { code: 'en-US', name: 'English (US)' }, | |
| { code: 'en-GB', name: 'English (UK)' }, | |
| { code: 'de-DE', name: 'Deutsch' }, | |
| { code: 'fr-FR', name: 'Français' }, | |
| { code: 'es-ES', name: 'Español' }, | |
| { code: 'it-IT', name: 'Italiano' }, | |
| { code: 'pt-BR', name: 'Português (BR)' }, | |
| { code: 'ja-JP', name: '日本語' }, | |
| { code: 'ko-KR', name: '한국어' }, | |
| { code: 'zh-CN', name: '中文' } | |
| ]; | |
| sttModels = [ | |
| { value: 'default', name: 'Default' }, | |
| { value: 'latest_short', name: 'Latest Short (Optimized for short audio)' }, | |
| { value: 'latest_long', name: 'Latest Long (Best accuracy)' }, | |
| { value: 'command_and_search', name: 'Command and Search' }, | |
| { value: 'phone_call', name: 'Phone Call (Optimized for telephony)' } | |
| ]; | |
| // API key visibility tracking | |
| showApiKeys: { [key: string]: boolean } = {}; | |
| // Memory leak prevention | |
| private destroyed$ = new Subject<void>(); | |
| constructor( | |
| private fb: FormBuilder, | |
| private apiService: ApiService, | |
| private environmentService: EnvironmentService, | |
| private snackBar: MatSnackBar | |
| ) { | |
| this.form = this.fb.group({ | |
| // LLM Provider | |
| llm_provider_name: ['', Validators.required], | |
| llm_provider_api_key: [''], | |
| llm_provider_endpoint: [''], | |
| // TTS Provider | |
| tts_provider_name: ['no_tts', Validators.required], | |
| tts_provider_api_key: [''], | |
| tts_provider_endpoint: [''], | |
| // STT Provider | |
| stt_provider_name: ['no_stt', Validators.required], | |
| stt_provider_api_key: [''], | |
| stt_provider_endpoint: [''], | |
| // STT Settings | |
| stt_settings: this.fb.group({ | |
| language: ['tr-TR'], | |
| speech_timeout_ms: [2000], | |
| enable_punctuation: [true], | |
| interim_results: [true], | |
| use_enhanced: [true], | |
| model: ['latest_long'], | |
| noise_reduction_level: [2], | |
| vad_sensitivity: [0.5] | |
| }) | |
| }); | |
| } | |
| ngOnInit() { | |
| this.loadEnvironment(); | |
| } | |
| ngOnDestroy() { | |
| this.destroyed$.next(); | |
| this.destroyed$.complete(); | |
| } | |
| // Safe getters for template | |
| get currentLLMProviderSafe(): ProviderConfig | null { | |
| return this.currentLLMProvider || null; | |
| } | |
| get currentTTSProviderSafe(): ProviderConfig | null { | |
| return this.currentTTSProvider || null; | |
| } | |
| get currentSTTProviderSafe(): ProviderConfig | null { | |
| return this.currentSTTProvider || null; | |
| } | |
| // API key masking methods | |
| maskApiKey(key?: string): string { | |
| if (!key) return ''; | |
| if (key.length <= 8) return '••••••••'; | |
| return key.substring(0, 4) + '••••' + key.substring(key.length - 4); | |
| } | |
| toggleApiKeyVisibility(fieldName: string): void { | |
| this.showApiKeys[fieldName] = !this.showApiKeys[fieldName]; | |
| } | |
| getApiKeyInputType(fieldName: string): string { | |
| return this.showApiKeys[fieldName] ? 'text' : 'password'; | |
| } | |
| formatApiKeyForDisplay(fieldName: string, value?: string): string { | |
| if (this.showApiKeys[fieldName]) { | |
| return value || ''; | |
| } | |
| return this.maskApiKey(value); | |
| } | |
| loadEnvironment(): void { | |
| this.loading = true; | |
| this.isLoading = true; | |
| this.apiService.getEnvironment() | |
| .pipe(takeUntil(this.destroyed$)) | |
| .subscribe({ | |
| next: (data: any) => { | |
| // Check if it's new format or legacy | |
| if (data.llm_provider) { | |
| this.handleNewFormat(data); | |
| } else { | |
| this.handleLegacyFormat(data); | |
| } | |
| this.loading = false; | |
| this.isLoading = false; | |
| }, | |
| error: (err) => { | |
| console.error('Failed to load environment:', err); | |
| this.snackBar.open('Failed to load environment configuration', 'Close', { | |
| duration: 3000, | |
| panelClass: ['error-snackbar'] | |
| }); | |
| this.loading = false; | |
| this.isLoading = false; | |
| } | |
| }); | |
| } | |
| handleNewFormat(data: EnvironmentConfig): void { | |
| // Update provider lists | |
| if (data.providers) { | |
| this.llmProviders = data.providers.filter(p => p.type === 'llm'); | |
| this.ttsProviders = data.providers.filter(p => p.type === 'tts'); | |
| this.sttProviders = data.providers.filter(p => p.type === 'stt'); | |
| } | |
| // Set form values | |
| this.form.patchValue({ | |
| llm_provider_name: data.llm_provider?.name || '', | |
| llm_provider_api_key: data.llm_provider?.api_key || '', | |
| llm_provider_endpoint: data.llm_provider?.endpoint || '', | |
| tts_provider_name: data.tts_provider?.name || 'no_tts', | |
| tts_provider_api_key: data.tts_provider?.api_key || '', | |
| tts_provider_endpoint: data.tts_provider?.endpoint || '', | |
| stt_provider_name: data.stt_provider?.name || 'no_stt', | |
| stt_provider_api_key: data.stt_provider?.api_key || '', | |
| stt_provider_endpoint: data.stt_provider?.endpoint || '' | |
| }); | |
| // Set internal prompt and parameter collection config | |
| this.internalPrompt = data.llm_provider?.settings?.internal_prompt || ''; | |
| this.parameterCollectionConfig = data.llm_provider?.settings?.parameter_collection_config || this.parameterCollectionConfig; | |
| // Update current providers | |
| this.updateCurrentProviders(); | |
| // Notify environment service | |
| if (data.tts_provider?.name !== 'no_tts') { | |
| this.environmentService.setTTSEnabled(true); | |
| } | |
| if (data.stt_provider?.name !== 'no_stt') { | |
| this.environmentService.setSTTEnabled(true); | |
| } | |
| if (data.stt_provider?.settings) { | |
| this.form.get('stt_settings')?.patchValue(data.stt_provider.settings); | |
| } | |
| } | |
| handleLegacyFormat(data: any): void { | |
| console.warn('Legacy environment format detected, using defaults'); | |
| // Set default providers if not present | |
| this.llmProviders = this.getDefaultProviders('llm'); | |
| this.ttsProviders = this.getDefaultProviders('tts'); | |
| this.sttProviders = this.getDefaultProviders('stt'); | |
| // Map legacy fields | |
| this.form.patchValue({ | |
| llm_provider_name: data.work_mode || 'spark', | |
| llm_provider_api_key: data.cloud_token || '', | |
| llm_provider_endpoint: data.spark_endpoint || '', | |
| tts_provider_name: data.tts_engine || 'no_tts', | |
| tts_provider_api_key: data.tts_engine_api_key || '', | |
| stt_provider_name: data.stt_engine || 'no_stt', | |
| stt_provider_api_key: data.stt_engine_api_key || '' | |
| }); | |
| this.internalPrompt = data.internal_prompt || ''; | |
| this.parameterCollectionConfig = data.parameter_collection_config || this.parameterCollectionConfig; | |
| this.updateCurrentProviders(); | |
| if (data.stt_settings) { | |
| this.form.get('stt_settings')?.patchValue(data.stt_settings); | |
| } | |
| } | |
| getDefaultProviders(type: string): ProviderConfig[] { | |
| const defaults: { [key: string]: ProviderConfig[] } = { | |
| llm: [ | |
| { | |
| type: 'llm', | |
| name: 'spark', | |
| display_name: 'Spark (YTU Cosmos)', | |
| requires_endpoint: true, | |
| requires_api_key: true, | |
| requires_repo_info: true, | |
| description: 'YTU Cosmos Spark LLM Service' | |
| }, | |
| { | |
| type: 'llm', | |
| name: 'gpt-4o', | |
| display_name: 'GPT-4o', | |
| requires_endpoint: false, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'OpenAI GPT-4o model' | |
| }, | |
| { | |
| type: 'llm', | |
| name: 'gpt-4o-mini', | |
| display_name: 'GPT-4o Mini', | |
| requires_endpoint: false, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'OpenAI GPT-4o Mini model' | |
| } | |
| ], | |
| tts: [ | |
| { | |
| type: 'tts', | |
| name: 'no_tts', | |
| display_name: 'No TTS', | |
| requires_endpoint: false, | |
| requires_api_key: false, | |
| requires_repo_info: false, | |
| description: 'Disable text-to-speech' | |
| }, | |
| { | |
| type: 'tts', | |
| name: 'elevenlabs', | |
| display_name: 'ElevenLabs', | |
| requires_endpoint: false, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'ElevenLabs TTS service' | |
| } | |
| ], | |
| stt: [ | |
| { | |
| type: 'stt', | |
| name: 'no_stt', | |
| display_name: 'No STT', | |
| requires_endpoint: false, | |
| requires_api_key: false, | |
| requires_repo_info: false, | |
| description: 'Disable speech-to-text' | |
| }, | |
| { | |
| type: 'stt', | |
| name: 'google', | |
| display_name: 'Google Cloud Speech', | |
| requires_endpoint: false, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'Google Cloud Speech-to-Text API' | |
| }, | |
| { | |
| type: 'stt', | |
| name: 'azure', | |
| display_name: 'Azure Speech Services', | |
| requires_endpoint: false, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'Azure Cognitive Services Speech' | |
| }, | |
| { | |
| type: 'stt', | |
| name: 'flicker', | |
| display_name: 'Flicker STT', | |
| requires_endpoint: true, | |
| requires_api_key: true, | |
| requires_repo_info: false, | |
| description: 'Flicker Speech Recognition Service' | |
| } | |
| ] | |
| }; | |
| return defaults[type] || []; | |
| } | |
| updateCurrentProviders(): void { | |
| const llmName = this.form.get('llm_provider_name')?.value; | |
| const ttsName = this.form.get('tts_provider_name')?.value; | |
| const sttName = this.form.get('stt_provider_name')?.value; | |
| this.currentLLMProvider = this.llmProviders.find(p => p.name === llmName); | |
| this.currentTTSProvider = this.ttsProviders.find(p => p.name === ttsName); | |
| this.currentSTTProvider = this.sttProviders.find(p => p.name === sttName); | |
| // Update form validators based on requirements | |
| this.updateFormValidators(); | |
| } | |
| updateFormValidators(): void { | |
| // LLM validators | |
| if (this.currentLLMProvider?.requires_api_key) { | |
| this.form.get('llm_provider_api_key')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('llm_provider_api_key')?.clearValidators(); | |
| } | |
| if (this.currentLLMProvider?.requires_endpoint) { | |
| this.form.get('llm_provider_endpoint')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('llm_provider_endpoint')?.clearValidators(); | |
| } | |
| // TTS validators | |
| if (this.currentTTSProvider?.requires_api_key) { | |
| this.form.get('tts_provider_api_key')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('tts_provider_api_key')?.clearValidators(); | |
| } | |
| // STT validators | |
| if (this.currentSTTProvider?.requires_api_key) { | |
| this.form.get('stt_provider_api_key')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('stt_provider_api_key')?.clearValidators(); | |
| } | |
| // STT endpoint validator | |
| if (this.currentSTTProvider?.requires_endpoint) { | |
| this.form.get('stt_provider_endpoint')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('stt_provider_endpoint')?.clearValidators(); | |
| } | |
| // Update validity | |
| this.form.get('llm_provider_api_key')?.updateValueAndValidity(); | |
| this.form.get('llm_provider_endpoint')?.updateValueAndValidity(); | |
| this.form.get('tts_provider_api_key')?.updateValueAndValidity(); | |
| this.form.get('stt_provider_api_key')?.updateValueAndValidity(); | |
| this.form.get('stt_provider_endpoint')?.updateValueAndValidity(); | |
| } | |
| onLLMProviderChange(value: string): void { | |
| this.currentLLMProvider = this.llmProviders.find(p => p.name === value); | |
| this.updateFormValidators(); | |
| // Reset fields if provider doesn't require them | |
| if (!this.currentLLMProvider?.requires_api_key) { | |
| this.form.get('llm_provider_api_key')?.setValue(''); | |
| } | |
| if (!this.currentLLMProvider?.requires_endpoint) { | |
| this.form.get('llm_provider_endpoint')?.setValue(''); | |
| } | |
| } | |
| onTTSProviderChange(value: string): void { | |
| this.currentTTSProvider = this.ttsProviders.find(p => p.name === value); | |
| this.updateFormValidators(); | |
| if (!this.currentTTSProvider?.requires_api_key) { | |
| this.form.get('tts_provider_api_key')?.setValue(''); | |
| } | |
| if (value !== this.form.get('stt_provider_name')?.value) { | |
| this.form.get('stt_provider_api_key')?.setValue(''); | |
| } | |
| // Provider-specific defaults | |
| if (value === 'google') { | |
| this.form.get('stt_settings')?.patchValue({ | |
| model: 'latest_long', | |
| use_enhanced: true | |
| }); | |
| } else if (value === 'azure') { | |
| this.form.get('stt_settings')?.patchValue({ | |
| model: 'default', | |
| use_enhanced: false | |
| }); | |
| } | |
| // STT endpoint validator | |
| if (this.currentSTTProvider?.requires_endpoint) { | |
| this.form.get('stt_provider_endpoint')?.setValidators(Validators.required); | |
| } else { | |
| this.form.get('stt_provider_endpoint')?.clearValidators(); | |
| } | |
| this.form.get('stt_provider_endpoint')?.updateValueAndValidity(); | |
| // Notify environment service | |
| this.environmentService.setTTSEnabled(value !== 'no_tts'); | |
| } | |
| onSTTProviderChange(value: string): void { | |
| this.currentSTTProvider = this.sttProviders.find(p => p.name === value); | |
| this.updateFormValidators(); | |
| if (!this.currentSTTProvider?.requires_api_key) { | |
| this.form.get('stt_provider_api_key')?.setValue(''); | |
| } | |
| // Notify environment service | |
| this.environmentService.setSTTEnabled(value !== 'no_stt'); | |
| } | |
| saveEnvironment(): void { | |
| if (this.form.invalid || this.saving) { | |
| this.snackBar.open('Please fix validation errors', 'Close', { | |
| duration: 3000, | |
| panelClass: ['error-snackbar'] | |
| }); | |
| return; | |
| } | |
| this.saving = true; | |
| const formValue = this.form.value; | |
| const saveData = { | |
| llm_provider: { | |
| name: formValue.llm_provider_name, | |
| api_key: formValue.llm_provider_api_key, | |
| endpoint: formValue.llm_provider_endpoint, | |
| settings: { | |
| internal_prompt: this.internalPrompt, | |
| parameter_collection_config: this.parameterCollectionConfig | |
| } | |
| }, | |
| tts_provider: { | |
| name: formValue.tts_provider_name, | |
| api_key: formValue.tts_provider_api_key, | |
| endpoint: formValue.tts_provider_endpoint, | |
| settings: {} | |
| }, | |
| stt_provider: { | |
| name: formValue.stt_provider_name, | |
| api_key: formValue.stt_provider_api_key, | |
| endpoint: formValue.stt_provider_endpoint, | |
| settings: formValue.stt_settings || {} | |
| } | |
| }; | |
| this.apiService.updateEnvironment(saveData as any) | |
| .pipe(takeUntil(this.destroyed$)) | |
| .subscribe({ | |
| next: () => { | |
| this.saving = false; | |
| this.snackBar.open('Environment configuration saved successfully', 'Close', { | |
| duration: 3000, | |
| panelClass: ['success-snackbar'] | |
| }); | |
| // Update environment service | |
| this.environmentService.updateEnvironment(saveData as any); | |
| // Clear form dirty state | |
| this.form.markAsPristine(); | |
| }, | |
| error: (error) => { | |
| this.saving = false; | |
| // Race condition handling | |
| if (error.status === 409) { | |
| const details = error.error?.details || {}; | |
| this.snackBar.open( | |
| `Settings were modified by ${details.last_update_user || 'another user'}. Please reload.`, | |
| 'Reload', | |
| { duration: 0 } | |
| ).onAction().subscribe(() => { | |
| this.loadEnvironment(); | |
| }); | |
| } else { | |
| this.snackBar.open( | |
| error.error?.detail || 'Failed to save environment configuration', | |
| 'Close', | |
| { | |
| duration: 5000, | |
| panelClass: ['error-snackbar'] | |
| } | |
| ); | |
| } | |
| } | |
| }); | |
| } | |
| // Icon helpers | |
| getLLMProviderIcon(provider: ProviderConfig | null): string { | |
| if (!provider || !provider.name) return 'smart_toy'; | |
| switch(provider.name) { | |
| case 'gpt-4o': | |
| case 'gpt-4o-mini': | |
| return 'psychology'; | |
| case 'spark': | |
| return 'auto_awesome'; | |
| default: | |
| return 'smart_toy'; | |
| } | |
| } | |
| getTTSProviderIcon(provider: ProviderConfig | null): string { | |
| if (!provider || !provider.name) return 'record_voice_over'; | |
| switch(provider.name) { | |
| case 'elevenlabs': | |
| return 'graphic_eq'; | |
| case 'blaze': | |
| return 'volume_up'; | |
| default: | |
| return 'record_voice_over'; | |
| } | |
| } | |
| getSTTProviderIcon(provider: ProviderConfig | null): string { | |
| if (!provider || !provider.name) return 'mic'; | |
| switch(provider.name) { | |
| case 'google': | |
| return 'g_translate'; | |
| case 'azure': | |
| return 'cloud'; | |
| case 'flicker': | |
| return 'mic_none'; | |
| default: | |
| return 'mic'; | |
| } | |
| } | |
| getProviderIcon(provider: ProviderConfig): string { | |
| switch(provider.type) { | |
| case 'llm': | |
| return this.getLLMProviderIcon(provider); | |
| case 'tts': | |
| return this.getTTSProviderIcon(provider); | |
| case 'stt': | |
| return this.getSTTProviderIcon(provider); | |
| default: | |
| return 'settings'; | |
| } | |
| } | |
| // Helper methods | |
| getApiKeyLabel(type: string): string { | |
| switch(type) { | |
| case 'llm': | |
| return this.currentLLMProvider?.name === 'spark' ? 'API Token' : 'API Key'; | |
| case 'tts': | |
| return 'API Key'; | |
| case 'stt': | |
| return this.currentSTTProvider?.name === 'google' ? 'Credentials JSON Path' : 'API Key'; | |
| default: | |
| return 'API Key'; | |
| } | |
| } | |
| getApiKeyPlaceholder(type: string): string { | |
| switch(type) { | |
| case 'llm': | |
| if (this.currentLLMProvider?.name === 'spark') return 'Enter Spark token'; | |
| if (this.currentLLMProvider?.name?.includes('gpt')) return 'sk-...'; | |
| return 'Enter API key'; | |
| case 'tts': | |
| return 'Enter TTS API key'; | |
| case 'stt': | |
| if (this.currentSTTProvider?.name === 'google') return '/path/to/credentials.json'; | |
| if (this.currentSTTProvider?.name === 'azure') return 'subscription_key|region'; | |
| return 'Enter STT API key'; | |
| default: | |
| return 'Enter API key'; | |
| } | |
| } | |
| getEndpointPlaceholder(type: string): string { | |
| switch(type) { | |
| case 'llm': | |
| return 'https://spark-api.example.com'; | |
| case 'tts': | |
| return 'https://tts-api.example.com'; | |
| case 'stt': | |
| return 'https://stt-api.example.com'; | |
| default: | |
| return 'https://api.example.com'; | |
| } | |
| } | |
| resetCollectionPrompt(): void { | |
| this.parameterCollectionConfig.collection_prompt = 'Please provide the following information:'; | |
| } | |
| testConnection(): void { | |
| const endpoint = this.form.get('llm_provider_endpoint')?.value; | |
| if (!endpoint) { | |
| this.snackBar.open('Please enter an endpoint URL', 'Close', { duration: 2000 }); | |
| return; | |
| } | |
| this.snackBar.open('Testing connection...', 'Close', { duration: 2000 }); | |
| // TODO: Implement actual connection test | |
| } | |
| } |