flare / flare-ui /src /app /dialogs /version-edit-dialog /version-edit-dialog.component.ts
ciyidogan's picture
Update flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.ts
59136fb verified
raw
history blame
19.3 kB
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<VersionEditDialogComponent>,
@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<any[]> {
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);
}
}