flare / flare-ui /src /app /components /environment /environment.component.ts
ciyidogan's picture
Update flare-ui/src/app/components/environment/environment.component.ts
b8839dd verified
raw
history blame
28.9 kB
import { Component, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatSliderModule } from '@angular/material/slider';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { ApiService, Environment, STTSettings } from '../../services/api.service';
import { EnvironmentService } from '../../services/environment.service';
@Component({
selector: 'app-environment',
standalone: true,
imports: [
CommonModule,
FormsModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatButtonModule,
MatIconModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatExpansionModule,
MatSliderModule,
MatTooltipModule,
MatCheckboxModule
],
template: `
<div class="environment-container">
<mat-card>
<mat-card-header>
<mat-card-title>
<mat-icon>settings</mat-icon>
Environment Configuration
</mat-card-title>
</mat-card-header>
<mat-card-content>
@if (isGPTMode()) {
<mat-card class="info-card">
<mat-card-content>
<mat-icon>info</mat-icon>
<div>
<strong>{{ environment.work_mode === 'gpt4o' ? 'GPT-4o' : 'GPT-4o Mini' }} Mode</strong>
<p>This mode uses OpenAI's API which has usage-based pricing.</p>
<p>Approximate cost: {{ environment.work_mode === 'gpt4o' ? '$0.01-0.03' : '$0.001-0.003' }} per conversation turn.</p>
</div>
</mat-card-content>
</mat-card>
}
<form (ngSubmit)="save()" #envForm="ngForm">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Work Mode</mat-label>
<mat-select
name="workMode"
[(ngModel)]="environment.work_mode"
(selectionChange)="onWorkModeChange()"
required
[disabled]="loading"
>
<mat-option value="hfcloud">HF Cloud</mat-option>
<mat-option value="cloud">Cloud</mat-option>
<mat-option value="on-premise">On-Premise</mat-option>
<mat-option value="gpt4o">GPT-4o</mat-option>
<mat-option value="gpt4o-mini">GPT-4o Mini</mat-option>
</mat-select>
<mat-icon matPrefix>cloud</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ getTokenLabel() }}</mat-label>
<input
matInput
type="password"
name="cloudToken"
[(ngModel)]="environment.cloud_token"
[disabled]="loading || environment.work_mode === 'on-premise'"
[placeholder]="getTokenPlaceholder()"
>
<mat-icon matPrefix>vpn_key</mat-icon>
<mat-hint>{{ isGPTMode() ? 'Required for OpenAI API access' : 'Required for HF Cloud and Cloud modes' }}</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Spark Endpoint</mat-label>
<input
matInput
type="url"
name="sparkEndpoint"
[(ngModel)]="environment.spark_endpoint"
[required]="!isGPTMode()"
[disabled]="loading || isGPTMode()"
placeholder="https://spark-service.example.com"
>
<mat-icon matPrefix>link</mat-icon>
<button
mat-icon-button
matSuffix
type="button"
(click)="testConnection()"
[disabled]="loading || !environment.spark_endpoint || isGPTMode()"
matTooltip="Test Connection"
>
<mat-icon>wifi_tethering</mat-icon>
</button>
@if (isGPTMode()) {
<mat-hint>Not required for GPT mode</mat-hint>
}
</mat-form-field>
<!-- TTS Configuration -->
<div class="engine-row">
<mat-form-field appearance="outline" class="engine-field">
<mat-label>TTS Engine</mat-label>
<mat-select
name="ttsEngine"
[(ngModel)]="environment.tts_engine"
(selectionChange)="onTTSEngineChange()"
[disabled]="loading"
>
<mat-option value="no_tts">No TTS</mat-option>
<mat-option value="elevenlabs">ElevenLabs</mat-option>
<mat-option value="blaze">Blaze (Coming Soon)</mat-option>
</mat-select>
<mat-icon matPrefix>record_voice_over</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline" class="api-key-field">
<mat-label>TTS API Key</mat-label>
<input
matInput
type="password"
name="ttsApiKey"
[(ngModel)]="environment.tts_engine_api_key"
[disabled]="loading || environment.tts_engine === 'no_tts'"
[required]="environment.tts_engine !== 'no_tts'"
placeholder="Enter TTS API key"
>
<mat-icon matPrefix>key</mat-icon>
</mat-form-field>
</div>
<!-- STT Configuration -->
<div class="engine-row">
<mat-form-field appearance="outline" class="engine-field">
<mat-label>STT Engine</mat-label>
<mat-select
name="sttEngine"
[(ngModel)]="environment.stt_engine"
(selectionChange)="onSTTEngineChange()"
[disabled]="loading"
>
<mat-option value="no_stt">No STT</mat-option>
<mat-option value="google">Google Cloud Speech-to-Text</mat-option>
<mat-option value="azure">Azure Speech Services (Coming Soon)</mat-option>
<mat-option value="amazon">Amazon Transcribe (Coming Soon)</mat-option>
<mat-option value="gpt4o_realtime">GPT-4o Realtime (Coming Soon)</mat-option>
<mat-option value="flicker">Flicker (Coming Soon)</mat-option>
</mat-select>
<mat-icon matPrefix>mic</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline" class="api-key-field">
<mat-label>STT API Key / Credentials</mat-label>
<input
matInput
type="password"
name="sttApiKey"
[(ngModel)]="environment.stt_engine_api_key"
[disabled]="loading || environment.stt_engine === 'no_stt'"
[required]="environment.stt_engine !== 'no_stt'"
[placeholder]="getSTTKeyPlaceholder()"
>
<mat-icon matPrefix>key</mat-icon>
<button mat-icon-button matSuffix
*ngIf="environment.stt_engine === 'google'"
(click)="fileInput.click()"
[disabled]="loading"
matTooltip="Upload Google credentials JSON">
<mat-icon>upload_file</mat-icon>
</button>
<input #fileInput type="file" accept=".json"
style="display: none"
(change)="onSTTCredentialsFileSelected($event)">
</mat-form-field>
</div>
<!-- STT Settings Panel -->
<mat-expansion-panel class="stt-settings-panel"
*ngIf="environment.stt_engine !== 'no_stt'"
[expanded]="sttSettingsExpanded">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>settings_voice</mat-icon>
STT Advanced Settings
</mat-panel-title>
<mat-panel-description>
Configure speech recognition parameters
</mat-panel-description>
</mat-expansion-panel-header>
<div class="stt-settings-grid" *ngIf="environment.stt_settings">
<mat-form-field appearance="outline">
<mat-label>Speech Timeout (ms)</mat-label>
<input matInput type="number"
name="speechTimeout"
[value]="environment.stt_settings.speech_timeout_ms"
(input)="updateSTTSetting('speech_timeout_ms', +$any($event.target).value)"
min="500" max="5000" step="100"
[disabled]="loading">
<mat-icon matPrefix>timer</mat-icon>
<mat-hint>Silence duration to end speech (500-5000ms)</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Noise Reduction Level</mat-label>
<mat-select name="noiseReduction"
[value]="environment.stt_settings.noise_reduction_level"
(selectionChange)="updateSTTSetting('noise_reduction_level', $event.value)"
[disabled]="loading">
<mat-option [value]="0">Off</mat-option>
<mat-option [value]="1">Low</mat-option>
<mat-option [value]="2">Medium</mat-option>
<mat-option [value]="3">High</mat-option>
</mat-select>
<mat-icon matPrefix>noise_aware</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>VAD Sensitivity</mat-label>
<mat-slider min="0" max="1" step="0.1"
[discrete]="true"
[displayWith]="formatVAD">
<input matSliderThumb
name="vadSensitivity"
[value]="environment.stt_settings.vad_sensitivity"
(input)="updateSTTSetting('vad_sensitivity', +$any($event.target).value)"
[disabled]="loading">
</mat-slider>
<mat-icon matPrefix>graphic_eq</mat-icon>
<mat-hint>Voice activity detection sensitivity (0=low, 1=high)</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Language</mat-label>
<mat-select name="sttLanguage"
[value]="environment.stt_settings.language"
(selectionChange)="updateSTTSetting('language', $event.value)"
[disabled]="loading">
<mat-option value="tr-TR">Turkish (tr-TR)</mat-option>
<mat-option value="en-US">English (en-US)</mat-option>
<mat-option value="de-DE">German (de-DE)</mat-option>
<mat-option value="fr-FR">French (fr-FR)</mat-option>
<mat-option value="es-ES">Spanish (es-ES)</mat-option>
</mat-select>
<mat-icon matPrefix>language</mat-icon>
</mat-form-field>
<mat-form-field appearance="outline"
*ngIf="environment.stt_engine === 'google'">
<mat-label>Model</mat-label>
<mat-select name="sttModel"
[value]="environment.stt_settings.model"
(selectionChange)="updateSTTSetting('model', $event.value)"
[disabled]="loading">
<mat-option value="latest_long">Latest Long (Best for conversations)</mat-option>
<mat-option value="command_and_search">Command & Search (Short queries)</mat-option>
<mat-option value="phone_call">Phone Call (Telephony)</mat-option>
<mat-option value="video">Video (Multiple speakers)</mat-option>
</mat-select>
<mat-icon matPrefix>model_training</mat-icon>
</mat-form-field>
<div class="checkbox-group">
<mat-checkbox name="useEnhanced"
[checked]="environment.stt_settings.use_enhanced"
(change)="updateSTTSetting('use_enhanced', $event.checked)"
[disabled]="loading || environment.stt_engine !== 'google'">
Use Enhanced Model
<mat-icon matTooltip="Better accuracy but higher cost" class="info-icon">info</mat-icon>
</mat-checkbox>
<mat-checkbox name="enablePunctuation"
[checked]="environment.stt_settings.enable_punctuation"
(change)="updateSTTSetting('enable_punctuation', $event.checked)"
[disabled]="loading">
Automatic Punctuation
</mat-checkbox>
<mat-checkbox name="interimResults"
[checked]="environment.stt_settings.interim_results"
(change)="updateSTTSetting('interim_results', $event.checked)"
[disabled]="loading">
Show Interim Results
</mat-checkbox>
</div>
</div>
<div class="stt-info-card" *ngIf="environment.stt_engine === 'google'">
<mat-icon>info</mat-icon>
<div>
<strong>Google Cloud Setup Required:</strong>
<ol>
<li>Create a Google Cloud project</li>
<li>Enable Speech-to-Text API</li>
<li>Create service account & download JSON key</li>
<li>Upload JSON key file as STT API Key</li>
</ol>
<p class="cost-info">Cost: ~$0.024/minute (standard), ~$0.036/minute (enhanced)</p>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel class="prompt-panel">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>psychology</mat-icon>
Internal System Prompt
</mat-panel-title>
<mat-panel-description>
Advanced configuration for LLM
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Internal Prompt</mat-label>
<textarea
matInput
name="internalPrompt"
[(ngModel)]="environment.internal_prompt"
[disabled]="loading"
rows="10"
placeholder="Enter internal system prompt..."
></textarea>
<mat-hint>This prompt will be prepended to all project prompts</mat-hint>
</mat-form-field>
</mat-expansion-panel>
<div class="form-actions">
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="loading || !envForm.valid || saving"
>
@if (saving) {
<mat-spinner diameter="20"></mat-spinner>
Saving...
} @else {
<mat-icon>save</mat-icon>
Save
}
</button>
<button
mat-raised-button
type="button"
(click)="reloadFromSpark()"
[disabled]="loading || isGPTMode()"
>
<mat-icon>refresh</mat-icon>
Reload from Spark
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
`,
styles: [`
.environment-container {
max-width: 800px;
margin: 0 auto;
}
mat-card-header {
margin-bottom: 24px;
mat-card-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 24px;
mat-icon {
font-size: 28px;
width: 28px;
height: 28px;
}
}
}
.info-card {
margin-bottom: 20px;
background-color: #e3f2fd;
mat-card-content {
display: flex;
gap: 16px;
align-items: flex-start;
mat-icon {
color: #1976d2;
margin-top: 4px;
}
p {
margin: 4px 0;
font-size: 14px;
}
}
}
.full-width {
width: 100%;
margin-bottom: 20px;
}
.engine-row {
display: flex;
gap: 16px;
align-items: flex-start;
margin-bottom: 20px;
.engine-field {
flex: 1;
}
.api-key-field {
flex: 1.5;
}
}
.prompt-panel {
margin-bottom: 20px;
mat-expansion-panel-header {
mat-panel-title {
display: flex;
align-items: center;
gap: 8px;
mat-icon {
color: #666;
}
}
}
}
.form-actions {
display: flex;
gap: 16px;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #e0e0e0;
button {
mat-spinner {
margin-right: 8px;
vertical-align: middle; // Spinner'ı hizalamak için ekleyin
}
mat-icon {
vertical-align: middle; // Icon'u hizalamak için ekleyin
margin-right: 4px; // Icon ile text arasına boşluk
}
}
}
.stt-settings-panel {
margin-bottom: 20px;
.stt-settings-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
.full-width {
grid-column: 1 / -1;
}
.checkbox-group {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
gap: 12px;
mat-checkbox {
display: flex;
align-items: center;
.info-icon {
font-size: 18px;
margin-left: 8px;
color: #666;
cursor: help;
}
}
}
}
.stt-info-card {
margin-top: 16px;
padding: 16px;
background-color: #e3f2fd;
border-radius: 4px;
display: flex;
gap: 16px;
mat-icon {
color: #1976d2;
flex-shrink: 0;
}
ol {
margin: 8px 0;
padding-left: 20px;
}
.cost-info {
margin-top: 8px;
font-size: 14px;
color: #666;
}
}
}
::ng-deep {
.mat-mdc-form-field-icon-prefix {
padding-right: 12px;
}
.mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: white;
}
.mat-expansion-panel-body {
padding: 16px 0 !important;
}
}
`]
})
export class EnvironmentComponent implements OnInit {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private environmentService = inject(EnvironmentService);
environment: Environment = {
work_mode: 'hfcloud',
cloud_token: '',
spark_endpoint: '',
internal_prompt: '',
tts_engine: 'no_tts',
tts_engine_api_key: '',
stt_engine: 'no_stt',
stt_engine_api_key: '',
stt_settings: {
speech_timeout_ms: 2000,
noise_reduction_level: 2,
vad_sensitivity: 0.5,
language: 'tr-TR',
model: 'latest_long',
use_enhanced: true,
enable_punctuation: true,
interim_results: true
}
};
sttSettingsExpanded = false;
loading = true;
saving = false;
ngOnInit() {
console.log('EnvironmentComponent ngOnInit started');
try {
this.loadEnvironment();
} catch (error) {
console.error('Error in ngOnInit:', error);
console.error('Stack trace:', error.stack);
}
}
ngAfterViewInit() {
console.log('EnvironmentComponent ngAfterViewInit');
console.log('Current environment:', this.environment);
console.log('STT settings:', this.environment.stt_settings);
}
loadEnvironment() {
console.log('loadEnvironment called');
this.loading = true;
this.apiService.getEnvironment().subscribe({
next: (env) => {
console.log('Environment loaded from API:', env);
this.environment = env;
// Debug check
console.log('Checking stt_settings:', this.environment.stt_settings);
if (!this.environment.stt_settings) {
console.log('No stt_settings found, creating defaults...');
this.environment.stt_settings = {
speech_timeout_ms: 2000,
noise_reduction_level: 2,
vad_sensitivity: 0.5,
language: 'tr-TR',
model: 'latest_long',
use_enhanced: true,
enable_punctuation: true,
interim_results: true
};
}
console.log('Final environment:', this.environment);
this.loading = false;
},
error: (err) => {
console.error('Error loading environment:', err);
this.snackBar.open('Failed to load environment configuration', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
this.loading = false;
}
});
}
onSTTCredentialsFileSelected(event: any) {
const file = event.target.files[0];
if (file && file.type === 'application/json') {
const reader = new FileReader();
reader.onload = (e: any) => {
try {
// Validate it's a valid JSON
const jsonContent = JSON.parse(e.target.result);
// Store the entire JSON content as the API key
this.environment.stt_engine_api_key = e.target.result;
this.snackBar.open('Google credentials loaded successfully', 'Close', {
duration: 3000
});
} catch (error) {
this.snackBar.open('Invalid JSON file', 'Close', {
duration: 3000,
panelClass: 'error-snackbar'
});
}
};
reader.readAsText(file);
}
}
updateSTTSetting(key: string, value: any) {
if (!this.environment.stt_settings) {
this.environment.stt_settings = {
speech_timeout_ms: 2000,
noise_reduction_level: 2,
vad_sensitivity: 0.5,
language: 'tr-TR',
model: 'latest_long',
use_enhanced: true,
enable_punctuation: true,
interim_results: true
};
}
(this.environment.stt_settings as any)[key] = value;
}
getTokenLabel(): string {
switch(this.environment.work_mode) {
case 'gpt4o':
case 'gpt4o-mini':
return 'OpenAI API Key';
case 'hfcloud':
case 'cloud':
return 'Cloud Token';
default:
return 'Cloud Token';
}
}
getTokenPlaceholder(): string {
switch(this.environment.work_mode) {
case 'gpt4o':
case 'gpt4o-mini':
return 'Enter OpenAI API key (sk-...)';
case 'hfcloud':
case 'cloud':
return 'Enter cloud token';
default:
return 'Enter cloud token';
}
}
isGPTMode(): boolean {
return this.environment.work_mode === 'gpt4o' || this.environment.work_mode === 'gpt4o-mini';
}
onWorkModeChange() {
if (this.environment.work_mode === 'on-premise') {
this.environment.cloud_token = '';
}
}
onSTTEngineChange() {
console.log('onSTTEngineChange called with:', this.environment.stt_engine);
console.log('Current stt_settings:', this.environment.stt_settings);
try {
if (this.environment.stt_engine === 'no_stt') {
this.environment.stt_engine_api_key = '';
this.sttSettingsExpanded = false;
} else {
// Debug log
console.log('Setting up STT settings...');
if (!this.environment.stt_settings) {
console.log('Creating new stt_settings...');
this.environment.stt_settings = {
speech_timeout_ms: 2000,
noise_reduction_level: 2,
vad_sensitivity: 0.5,
language: 'tr-TR',
model: 'latest_long',
use_enhanced: true,
enable_punctuation: true,
interim_results: true
};
}
this.sttSettingsExpanded = true;
console.log('STT settings after setup:', this.environment.stt_settings);
}
} catch (error) {
console.error('Error in onSTTEngineChange:', error);
console.error('Stack trace:', error.stack);
}
}
formatVAD(value: number): string {
return value.toFixed(1);
}
getSTTKeyPlaceholder(): string {
switch(this.environment.stt_engine) {
case 'google':
return 'Google service account JSON content';
case 'azure':
return 'Azure subscription key';
case 'amazon':
return 'AWS access key';
case 'gpt4o_realtime':
return 'OpenAI API key';
default:
return 'Enter API key';
}
}
onSTTEngineChange() {
if (this.environment.stt_engine === 'no_stt') {
this.environment.stt_engine_api_key = '';
this.sttSettingsExpanded = false;
} else {
// Ensure stt_settings exists when enabling STT
if (!this.environment.stt_settings) {
this.environment.stt_settings = {
speech_timeout_ms: 2000,
noise_reduction_level: 2,
vad_sensitivity: 0.5,
language: 'tr-TR',
model: 'latest_long',
use_enhanced: true,
enable_punctuation: true,
interim_results: true
};
}
this.sttSettingsExpanded = true;
}
}
save() {
this.saving = true;
this.apiService.updateEnvironment(this.environment).subscribe({
next: () => {
// Environment service'i güncelle
this.environmentService.updateEnvironment(this.environment);
this.snackBar.open('Environment configuration saved successfully', 'Close', {
duration: 3000
});
this.saving = false;
},
error: (err) => {
this.snackBar.open(
err.error?.detail || 'Failed to save configuration',
'Close',
{ duration: 5000, panelClass: 'error-snackbar' }
);
this.saving = false;
}
});
}
testConnection() {
this.snackBar.open('Testing connection to Spark endpoint...', undefined, {
duration: 2000
});
// TODO: Implement actual connection test
setTimeout(() => {
this.snackBar.open('Connection successful!', 'Close', {
duration: 3000
});
}, 2000);
}
reloadFromSpark() {
if (this.isGPTMode()) {
return;
}
this.snackBar.open('Reloading configuration from Spark...', undefined, {
duration: 2000
});
setTimeout(() => {
this.loadEnvironment();
this.snackBar.open('Configuration reloaded', 'Close', {
duration: 3000
});
}, 1000);
}
}