import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ApiService } from '../services/api.service'; import { finalize } from 'rxjs/operators'; 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; } @Component({ selector: 'app-environment', templateUrl: './environment.component.html', styleUrls: ['./environment.component.scss'] }) export class EnvironmentComponent implements OnInit { environmentForm!: FormGroup; loading = false; saving = false; showInternalPrompt = false; showParameterCollection = false; // Provider lists allProviders: ProviderConfig[] = []; llmProviders: ProviderConfig[] = []; ttsProviders: ProviderConfig[] = []; sttProviders: ProviderConfig[] = []; // Current provider configs currentLLMProvider?: ProviderConfig; currentTTSProvider?: ProviderConfig; currentSTTProvider?: ProviderConfig; constructor( private fb: FormBuilder, private apiService: ApiService, private snackBar: MatSnackBar ) {} ngOnInit(): void { this.initializeForm(); this.loadEnvironment(); } initializeForm(): void { this.environmentForm = this.fb.group({ // LLM Provider llm_provider_name: ['', Validators.required], llm_provider_api_key: [''], llm_provider_endpoint: [''], internal_prompt: [''], // Parameter Collection Config max_params_per_question: [2, [Validators.min(1), Validators.max(5)]], retry_unanswered: [true], collection_prompt: [''], // TTS Provider tts_provider_name: ['no_tts'], tts_provider_api_key: [''], tts_provider_endpoint: [''], tts_use_ssml: [false], // STT Provider stt_provider_name: ['no_stt'], stt_provider_api_key: [''], stt_provider_endpoint: [''], stt_speech_timeout_ms: [2000], stt_noise_reduction_level: [2], stt_vad_sensitivity: [0.5], stt_language: ['tr-TR'], stt_model: ['latest_long'], stt_use_enhanced: [true], stt_enable_punctuation: [true], stt_interim_results: [true] }); // Subscribe to provider changes this.environmentForm.get('llm_provider_name')?.valueChanges.subscribe(name => { this.onLLMProviderChange(name); }); this.environmentForm.get('tts_provider_name')?.valueChanges.subscribe(name => { this.onTTSProviderChange(name); }); this.environmentForm.get('stt_provider_name')?.valueChanges.subscribe(name => { this.onSTTProviderChange(name); }); } loadEnvironment(): void { this.loading = true; this.apiService.getEnvironment() .pipe(finalize(() => this.loading = false)) .subscribe({ next: (data) => { // Store providers this.allProviders = data.providers || []; this.llmProviders = this.allProviders.filter(p => p.type === 'llm'); this.ttsProviders = [{ type: 'tts', name: 'no_tts', display_name: 'No TTS', requires_endpoint: false, requires_api_key: false, requires_repo_info: false }, ...this.allProviders.filter(p => p.type === 'tts')]; this.sttProviders = [{ type: 'stt', name: 'no_stt', display_name: 'No STT', requires_endpoint: false, requires_api_key: false, requires_repo_info: false }, ...this.allProviders.filter(p => p.type === 'stt')]; // Set form values this.environmentForm.patchValue({ // LLM Provider llm_provider_name: data.llm_provider?.name || '', llm_provider_api_key: data.llm_provider?.api_key || '', llm_provider_endpoint: data.llm_provider?.endpoint || '', internal_prompt: data.llm_provider?.settings?.internal_prompt || '', // Parameter Collection max_params_per_question: data.llm_provider?.settings?.parameter_collection_config?.max_params_per_question || 2, retry_unanswered: data.llm_provider?.settings?.parameter_collection_config?.retry_unanswered ?? true, collection_prompt: data.llm_provider?.settings?.parameter_collection_config?.collection_prompt || '', // TTS Provider 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 || '', tts_use_ssml: data.tts_provider?.settings?.use_ssml || false, // STT Provider 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 || '', stt_speech_timeout_ms: data.stt_provider?.settings?.speech_timeout_ms || 2000, stt_noise_reduction_level: data.stt_provider?.settings?.noise_reduction_level || 2, stt_vad_sensitivity: data.stt_provider?.settings?.vad_sensitivity || 0.5, stt_language: data.stt_provider?.settings?.language || 'tr-TR', stt_model: data.stt_provider?.settings?.model || 'latest_long', stt_use_enhanced: data.stt_provider?.settings?.use_enhanced ?? true, stt_enable_punctuation: data.stt_provider?.settings?.enable_punctuation ?? true, stt_interim_results: data.stt_provider?.settings?.interim_results ?? true }); // Trigger provider change handlers this.onLLMProviderChange(data.llm_provider?.name || ''); this.onTTSProviderChange(data.tts_provider?.name || 'no_tts'); this.onSTTProviderChange(data.stt_provider?.name || 'no_stt'); }, error: (error) => { this.snackBar.open('Failed to load environment configuration', 'Close', { duration: 3000, panelClass: ['error-snackbar'] }); } }); } onLLMProviderChange(name: string): void { this.currentLLMProvider = this.llmProviders.find(p => p.name === name); if (this.currentLLMProvider) { // Update validators const apiKeyControl = this.environmentForm.get('llm_provider_api_key'); const endpointControl = this.environmentForm.get('llm_provider_endpoint'); if (this.currentLLMProvider.requires_api_key) { apiKeyControl?.setValidators([Validators.required]); } else { apiKeyControl?.clearValidators(); } if (this.currentLLMProvider.requires_endpoint) { endpointControl?.setValidators([Validators.required, Validators.pattern('https?://.+')]); } else { endpointControl?.clearValidators(); } apiKeyControl?.updateValueAndValidity(); endpointControl?.updateValueAndValidity(); } } onTTSProviderChange(name: string): void { this.currentTTSProvider = this.ttsProviders.find(p => p.name === name); if (this.currentTTSProvider) { const apiKeyControl = this.environmentForm.get('tts_provider_api_key'); const endpointControl = this.environmentForm.get('tts_provider_endpoint'); if (this.currentTTSProvider.requires_api_key && name !== 'no_tts') { apiKeyControl?.setValidators([Validators.required]); } else { apiKeyControl?.clearValidators(); } if (this.currentTTSProvider.requires_endpoint && name !== 'no_tts') { endpointControl?.setValidators([Validators.required, Validators.pattern('https?://.+')]); } else { endpointControl?.clearValidators(); } apiKeyControl?.updateValueAndValidity(); endpointControl?.updateValueAndValidity(); } } onSTTProviderChange(name: string): void { this.currentSTTProvider = this.sttProviders.find(p => p.name === name); if (this.currentSTTProvider) { const apiKeyControl = this.environmentForm.get('stt_provider_api_key'); const endpointControl = this.environmentForm.get('stt_provider_endpoint'); if (this.currentSTTProvider.requires_api_key && name !== 'no_stt') { apiKeyControl?.setValidators([Validators.required]); } else { apiKeyControl?.clearValidators(); } if (this.currentSTTProvider.requires_endpoint && name !== 'no_stt') { endpointControl?.setValidators([Validators.required, Validators.pattern('https?://.+')]); } else { endpointControl?.clearValidators(); } apiKeyControl?.updateValueAndValidity(); endpointControl?.updateValueAndValidity(); } } save(): void { if (this.environmentForm.invalid) { Object.keys(this.environmentForm.controls).forEach(key => { const control = this.environmentForm.get(key); if (control && control.invalid) { control.markAsTouched(); } }); return; } this.saving = true; const formValue = this.environmentForm.value; // Build environment update payload const payload = { llm_provider: { name: formValue.llm_provider_name, api_key: formValue.llm_provider_api_key || null, endpoint: formValue.llm_provider_endpoint || null, settings: { internal_prompt: formValue.internal_prompt || null, parameter_collection_config: { max_params_per_question: formValue.max_params_per_question, retry_unanswered: formValue.retry_unanswered, collection_prompt: formValue.collection_prompt || null } } }, tts_provider: { name: formValue.tts_provider_name, api_key: formValue.tts_provider_api_key || null, endpoint: formValue.tts_provider_endpoint || null, settings: { use_ssml: formValue.tts_use_ssml } }, stt_provider: { name: formValue.stt_provider_name, api_key: formValue.stt_provider_api_key || null, endpoint: formValue.stt_provider_endpoint || null, settings: { speech_timeout_ms: formValue.stt_speech_timeout_ms, noise_reduction_level: formValue.stt_noise_reduction_level, vad_sensitivity: formValue.stt_vad_sensitivity, language: formValue.stt_language, model: formValue.stt_model, use_enhanced: formValue.stt_use_enhanced, enable_punctuation: formValue.stt_enable_punctuation, interim_results: formValue.stt_interim_results } } }; this.apiService.updateEnvironment(payload) .pipe(finalize(() => this.saving = false)) .subscribe({ next: () => { this.snackBar.open('Environment configuration saved successfully', 'Close', { duration: 3000, panelClass: ['success-snackbar'] }); }, error: (error) => { this.snackBar.open( error.error?.detail || 'Failed to save environment configuration', 'Close', { duration: 5000, panelClass: ['error-snackbar'] } ); } }); } testConnection(): void { const endpoint = this.environmentForm.get('llm_provider_endpoint')?.value; const apiKey = this.environmentForm.get('llm_provider_api_key')?.value; const provider = this.environmentForm.get('llm_provider_name')?.value; if (!endpoint) { this.snackBar.open('Please enter an endpoint URL', 'Close', { duration: 3000, panelClass: ['error-snackbar'] }); return; } this.loading = true; this.apiService.testConnection({ endpoint, api_key: apiKey, provider }) .pipe(finalize(() => this.loading = false)) .subscribe({ next: (result) => { if (result.success) { this.snackBar.open('Connection successful!', 'Close', { duration: 3000, panelClass: ['success-snackbar'] }); } else { this.snackBar.open(result.message || 'Connection failed', 'Close', { duration: 5000, panelClass: ['error-snackbar'] }); } }, error: (error) => { this.snackBar.open('Connection test failed', 'Close', { duration: 3000, panelClass: ['error-snackbar'] }); } }); } toggleInternalPrompt(): void { this.showInternalPrompt = !this.showInternalPrompt; } toggleParameterCollection(): void { this.showParameterCollection = !this.showParameterCollection; } isLLMEndpointRequired(): boolean { return this.currentLLMProvider?.requires_endpoint || false; } isLLMApiKeyRequired(): boolean { return this.currentLLMProvider?.requires_api_key || false; } isTTSEnabled(): boolean { return this.environmentForm.get('tts_provider_name')?.value !== 'no_tts'; } isSTTEnabled(): boolean { return this.environmentForm.get('stt_provider_name')?.value !== 'no_stt'; } getLLMProviderDescription(): string { return this.currentLLMProvider?.description || ''; } getTTSProviderDescription(): string { return this.currentTTSProvider?.description || ''; } getSTTProviderDescription(): string { return this.currentSTTProvider?.description || ''; } }