flare / flare-ui /src /app /components /environment /environment.component.ts
ciyidogan's picture
Upload 118 files
9f79da5 verified
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[];
}
@Component({
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
}
}