import { Component, Inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray, FormsModule } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog'; import { MatTabsModule } from '@angular/material/tabs'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTableModule } from '@angular/material/table'; import { MatChipsModule } from '@angular/material/chips'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatDividerModule } from '@angular/material/divider'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatListModule } from '@angular/material/list'; import { ApiService, Project, Version } from '../../services/api.service'; import ConfirmDialogComponent from '../confirm-dialog/confirm-dialog.component'; @Component({ selector: 'app-version-edit-dialog', standalone: true, imports: [ CommonModule, ReactiveFormsModule, FormsModule, // BU SATIRI EKLE MatDialogModule, MatTabsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatCheckboxModule, MatButtonModule, MatIconModule, MatSnackBarModule, MatTableModule, MatChipsModule, MatExpansionModule, MatDividerModule, MatProgressBarModule, MatListModule ], templateUrl: './version-edit-dialog.component.html', styleUrls: ['./version-edit-dialog.component.scss'] }) export default class VersionEditDialogComponent implements OnInit { project: Project; versions: Version[] = []; selectedVersion: Version | null = null; versionForm!: FormGroup; loading = false; saving = false; publishing = false; creating = false; selectedTabIndex = 0; testUserMessage = ''; testResult: any = null; testing = false; constructor( private fb: FormBuilder, private apiService: ApiService, private snackBar: MatSnackBar, private dialog: MatDialog, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) { this.project = data.project; this.versions = [...this.project.versions].sort((a, b) => b.id - a.id); } ngOnInit() { this.initializeForm(); // Select the latest unpublished version or the latest version const unpublished = this.versions.find(v => !v.published); this.selectedVersion = unpublished || this.versions[0] || null; if (this.selectedVersion) { this.loadVersion(this.selectedVersion); } } initializeForm() { this.versionForm = this.fb.group({ id: [{value: '', disabled: true}], caption: ['', Validators.required], published: [{value: false, disabled: true}], general_prompt: ['', Validators.required], llm: this.fb.group({ repo_id: ['', Validators.required], generation_config: this.fb.group({ max_new_tokens: [256, [Validators.required, Validators.min(1), Validators.max(2048)]], temperature: [0.2, [Validators.required, Validators.min(0), Validators.max(2)]], top_p: [0.8, [Validators.required, Validators.min(0), Validators.max(1)]], repetition_penalty: [1.1, [Validators.required, Validators.min(1), Validators.max(2)]] }), use_fine_tune: [false], fine_tune_zip: [''] }), intents: this.fb.array([]), last_update_date: [''] }); // Watch for fine-tune toggle this.versionForm.get('llm.use_fine_tune')?.valueChanges.subscribe(useFineTune => { const fineTuneControl = this.versionForm.get('llm.fine_tune_zip'); if (useFineTune) { fineTuneControl?.setValidators([Validators.required]); } else { fineTuneControl?.clearValidators(); fineTuneControl?.setValue(''); } fineTuneControl?.updateValueAndValidity(); }); } loadVersion(version: Version) { this.selectedVersion = version; // Reset form to initial state this.initializeForm(); // Populate basic fields this.versionForm.patchValue({ id: version.id, caption: version.caption, published: version.published, general_prompt: version.general_prompt || '', llm: version.llm || { repo_id: '', generation_config: { max_new_tokens: 256, temperature: 0.2, top_p: 0.8, repetition_penalty: 1.1 }, use_fine_tune: false, fine_tune_zip: '' }, last_update_date: version.last_update_date || '' }); // Populate intents const intentsArray = this.versionForm.get('intents') as FormArray; if (version.intents) { version.intents.forEach(intent => { const intentFormGroup = this.createIntentFormGroup(intent); // Parameters'ı ayrı olarak ekle if (intent.parameters) { this.populateIntentParameters(intentFormGroup, intent.parameters); } intentsArray.push(intentFormGroup); }); } // Enable/disable form based on published status if (version.published) { this.versionForm.disable(); this.snackBar.open('This version is published and cannot be edited', 'OK', { duration: 3000 }); } else { this.versionForm.enable(); this.versionForm.get('id')?.disable(); this.versionForm.get('published')?.disable(); } } createIntentFormGroup(intent: any = {}): FormGroup { return this.fb.group({ name: [intent.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9-]+$/)]], caption: [intent.caption || ''], locale: [intent.locale || 'tr-TR'], detection_prompt: [intent.detection_prompt || '', Validators.required], examples: this.fb.array(intent.examples || []), parameters: this.fb.array([]), // Boş array ile başla action: [intent.action || '', Validators.required], fallback_timeout_prompt: [intent.fallback_timeout_prompt || ''], fallback_error_prompt: [intent.fallback_error_prompt || ''] }); } private populateIntentParameters(intentFormGroup: FormGroup, parameters: any[]) { const parametersArray = intentFormGroup.get('parameters') as FormArray; parameters.forEach(param => { parametersArray.push(this.createParameterFormGroup(param)); }); } createParameterFormGroup(param: any = {}): FormGroup { return this.fb.group({ name: [param.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]], caption: [param.caption || ''], type: [param.type || 'str', Validators.required], required: [param.required !== false], variable_name: [param.variable_name || '', Validators.required], extraction_prompt: [param.extraction_prompt || ''], validation_regex: [param.validation_regex || ''], invalid_prompt: [param.invalid_prompt || ''], type_error_prompt: [param.type_error_prompt || ''] }); } get intents() { return this.versionForm.get('intents') as FormArray; } getIntentParameters(intentIndex: number): FormArray { return this.intents.at(intentIndex).get('parameters') as FormArray; } getIntentExamples(intentIndex: number): FormArray { return this.intents.at(intentIndex).get('examples') as FormArray; } addIntent() { this.intents.push(this.createIntentFormGroup()); } removeIntent(index: number) { const intent = this.intents.at(index).value; const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '400px', data: { title: 'Delete Intent', message: `Are you sure you want to delete intent "${intent.name}"?`, confirmText: 'Delete', confirmColor: 'warn' } }); dialogRef.afterClosed().subscribe(confirmed => { if (confirmed) { this.intents.removeAt(index); } }); } async editIntent(intentIndex: number) { const { default: IntentEditDialogComponent } = await import('../intent-edit-dialog/intent-edit-dialog.component'); const intent = this.intents.at(intentIndex); const dialogRef = this.dialog.open(IntentEditDialogComponent, { width: '90vw', maxWidth: '1000px', data: { intent: intent.value, project: this.project, apis: await this.getAvailableAPIs() } }); dialogRef.afterClosed().subscribe(result => { if (result) { // Update the intent in the form array intent.patchValue(result); } }); } addParameter(intentIndex: number) { const parameters = this.getIntentParameters(intentIndex); parameters.push(this.createParameterFormGroup()); } removeParameter(intentIndex: number, paramIndex: number) { const parameters = this.getIntentParameters(intentIndex); parameters.removeAt(paramIndex); } addExample(intentIndex: number, example: string) { if (example.trim()) { const examples = this.getIntentExamples(intentIndex); examples.push(this.fb.control(example)); } } removeExample(intentIndex: number, exampleIndex: number) { const examples = this.getIntentExamples(intentIndex); examples.removeAt(exampleIndex); } async createVersion() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '500px', data: { title: 'Create New Version', message: 'Which version would you like to use as a base for the new version?', confirmText: 'Create', showVersionSelect: true, versions: this.versions } }); dialogRef.afterClosed().subscribe(async (sourceVersionId) => { if (sourceVersionId) { this.creating = true; try { const result = await this.apiService.createVersion(this.project.id, { source_version_id: sourceVersionId, caption: 'New Version' }).toPromise(); this.snackBar.open('Version created successfully', 'Close', { duration: 3000 }); // Reload project to get new version await this.reloadProject(); // Select the new version const newVersion = this.versions.find(v => v.id === result.id); if (newVersion) { this.loadVersion(newVersion); } } catch (error: any) { this.snackBar.open(error.error?.detail || 'Failed to create version', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } finally { this.creating = false; } } }); } async saveVersion() { if (this.versionForm.invalid || !this.selectedVersion) { this.snackBar.open('Please fix all validation errors', 'Close', { duration: 3000 }); return; } this.saving = true; // updateData'yı try bloğunun dışında tanımla const formValue = this.versionForm.getRawValue(); const intents = formValue.intents.map((intent: any) => ({ ...intent, examples: intent.examples || [] })); const updateData = { caption: formValue.caption, general_prompt: formValue.general_prompt, llm: formValue.llm, intents: intents, last_update_date: formValue.last_update_date }; try { await this.apiService.updateVersion( this.project.id, this.selectedVersion.id, updateData ).toPromise(); this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 }); // Update last_update_date for next save this.versionForm.patchValue({ last_update_date: new Date().toISOString() }); } catch (error: any) { if (error.status === 409 || error.requiresReload) { // Race condition detected const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '500px', data: { title: 'Version Modified', message: 'This version was modified by another user. Do you want to reload and lose your changes, or save anyway?', confirmText: 'Reload', cancelText: 'Save Anyway', confirmColor: 'primary' } }); dialogRef.afterClosed().subscribe(async (shouldReload) => { if (shouldReload) { // Reload version await this.reloadProject(); if (this.selectedVersion) { const updated = this.versions.find(v => v.id === this.selectedVersion!.id); if (updated) { this.loadVersion(updated); } } } else { // Force save by removing last_update_date check const forceUpdateData = { ...updateData }; // updateData artık erişilebilir delete forceUpdateData.last_update_date; try { await this.apiService.updateVersion( this.project.id, this.selectedVersion!.id, forceUpdateData, true // force parameter ).toPromise(); this.snackBar.open('Version saved (forced)', 'Close', { duration: 3000 }); await this.reloadProject(); } catch (err: any) { this.snackBar.open(err.error?.detail || 'Failed to save version', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } } }); } else { this.snackBar.open(error.error?.detail || 'Failed to save version', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } } finally { this.saving = false; } } async publishVersion() { if (!this.selectedVersion) return; const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '500px', data: { title: 'Publish Version', message: `Are you sure you want to publish version "${this.selectedVersion.caption}"? This will unpublish all other versions.`, confirmText: 'Publish', confirmColor: 'primary' } }); dialogRef.afterClosed().subscribe(async (confirmed) => { if (confirmed && this.selectedVersion) { this.publishing = true; try { await this.apiService.publishVersion( this.project.id, this.selectedVersion.id ).toPromise(); this.snackBar.open('Version published successfully', 'Close', { duration: 3000 }); // Reload to get updated data await this.reloadProject(); } catch (error: any) { this.snackBar.open(error.error?.detail || 'Failed to publish version', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } finally { this.publishing = false; } } }); } async deleteVersion() { if (!this.selectedVersion || this.selectedVersion.published) return; const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '400px', data: { title: 'Delete Version', message: `Are you sure you want to delete version "${this.selectedVersion.caption}"?`, confirmText: 'Delete', confirmColor: 'warn' } }); dialogRef.afterClosed().subscribe(async (confirmed) => { if (confirmed && this.selectedVersion) { try { await this.apiService.deleteVersion( this.project.id, this.selectedVersion.id ).toPromise(); this.snackBar.open('Version deleted successfully', 'Close', { duration: 3000 }); // Reload and select another version await this.reloadProject(); if (this.versions.length > 0) { this.loadVersion(this.versions[0]); } else { this.selectedVersion = null; } } catch (error: any) { this.snackBar.open(error.error?.detail || 'Failed to delete version', 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } } }); } async testIntentDetection() { if (!this.testUserMessage.trim()) { this.snackBar.open('Please enter a test message', 'Close', { duration: 3000 }); return; } this.testing = true; this.testResult = null; // Simulate intent detection test setTimeout(() => { // This is a mock - in real implementation, this would call the Spark service const intents = this.versionForm.get('intents')?.value || []; // Simple matching for demo let detectedIntent = null; let confidence = 0; for (const intent of intents) { for (const example of intent.examples || []) { if (this.testUserMessage.toLowerCase().includes(example.toLowerCase())) { detectedIntent = intent.name; confidence = 0.95; break; } } if (detectedIntent) break; } // Random detection for demo if (!detectedIntent && intents.length > 0) { const randomIntent = intents[Math.floor(Math.random() * intents.length)]; detectedIntent = randomIntent.name; confidence = 0.65; } this.testResult = { success: true, intent: detectedIntent, confidence: confidence, parameters: detectedIntent ? this.extractTestParameters(detectedIntent) : [] }; this.testing = false; }, 1500); } private extractTestParameters(intentName: string): any[] { // Mock parameter extraction const intent = this.intents.value.find((i: any) => i.name === intentName); if (!intent) return []; return intent.parameters.map((param: any) => ({ name: param.name, value: param.type === 'date' ? '2025-06-15' : 'test_value', extracted: Math.random() > 0.3 })); } async getAvailableAPIs(): Promise { try { return await this.apiService.getAPIs().toPromise() || []; } catch { return []; } } private async reloadProject() { this.loading = true; try { const projects = await this.apiService.getProjects().toPromise() || []; const updatedProject = projects.find(p => p.id === this.project.id); if (updatedProject) { this.project = updatedProject; this.versions = [...updatedProject.versions].sort((a, b) => b.id - a.id); } } catch (error) { console.error('Failed to reload project:', error); } finally { this.loading = false; } } compareVersions() { // TODO: Implement version comparison this.snackBar.open('Version comparison coming soon', 'Close', { duration: 3000 }); } close() { this.dialogRef.close(true); } }