flare / flare-ui /src /app /dialogs /version-edit-dialog /version-edit-dialog.component.ts
ciyidogan's picture
Upload 118 files
9f79da5 verified
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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatBadgeModule } from '@angular/material/badge';
import { ApiService, Project, Version } from '../../services/api.service';
import { LocaleManagerService } from '../../services/locale-manager.service';
import ConfirmDialogComponent from '../confirm-dialog/confirm-dialog.component';
// Interfaces for multi-language support
interface LocalizedExample {
locale_code: string;
example: string;
}
interface LocalizedCaption {
locale_code: string;
caption: string;
}
interface Locale {
code: string;
name: string;
}
@Component({
selector: 'app-version-edit-dialog',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
FormsModule,
MatDialogModule,
MatTabsModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatCheckboxModule,
MatButtonModule,
MatIconModule,
MatSnackBarModule,
MatTableModule,
MatChipsModule,
MatExpansionModule,
MatDividerModule,
MatProgressBarModule,
MatListModule,
MatProgressSpinnerModule,
MatBadgeModule
],
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;
isDirty = false;
testing = false;
selectedTabIndex = 0;
testUserMessage = '';
testResult: any = null;
// Multi-language support
selectedExampleLocale: string = 'tr';
availableLocales: Locale[] = [];
constructor(
private fb: FormBuilder,
private apiService: ApiService,
private localeService: LocaleManagerService,
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.no - a.no);
this.selectedExampleLocale = this.project.default_locale || 'tr';
}
ngOnInit() {
this.initializeForm();
this.loadAvailableLocales();
// 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);
}
this.versionForm.valueChanges.subscribe(() => {
this.isDirty = true;
});
}
initializeForm() {
this.versionForm = this.fb.group({
no: [{value: '', disabled: true}],
caption: ['', Validators.required],
published: [{value: false, disabled: true}],
general_prompt: ['', Validators.required],
welcome_prompt: [''], // Added welcome_prompt field
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();
});
}
async loadAvailableLocales() {
// Get supported locales from project
const supportedCodes = [
this.project.default_locale,
...(this.project.supported_locales || [])
].filter(Boolean);
// Get locale details
for (const code of supportedCodes) {
if (!code) continue; // Skip undefined/null values
try {
const localeInfo = await this.localeService.getLocaleDetails(code).toPromise();
if (localeInfo) {
this.availableLocales.push({
code: localeInfo.code,
name: localeInfo.name
});
}
} catch (error) {
// Use fallback for known locales
const fallbackNames: { [key: string]: string } = {
'tr': 'Türkçe',
'en': 'English',
'de': 'Deutsch',
'fr': 'Français',
'es': 'Español'
};
if (code && fallbackNames[code]) {
this.availableLocales.push({
code: code,
name: fallbackNames[code]
});
}
}
}
}
getAvailableLocales(): Locale[] {
return this.availableLocales;
}
getLocaleName(localeCode: string): string {
const locale = this.availableLocales.find(l => l.code === localeCode);
return locale?.name || localeCode;
}
loadVersion(version: Version) {
this.selectedVersion = version;
// Debug published status
console.log('Loading version:', version.no, 'Published:', version.published);
// Form değerlerini set et
this.versionForm.patchValue({
no: version.no,
caption: version.caption || '',
published: version.published || false,
general_prompt: (version as any).general_prompt || '',
welcome_prompt: (version as any).welcome_prompt || '', // Added welcome_prompt
last_update_date: version.last_update_date || ''
});
// LLM config'i ayrı set et
if ((version as any).llm) {
this.versionForm.patchValue({
llm: {
repo_id: (version as any).llm.repo_id || '',
generation_config: (version as any).llm.generation_config || {
max_new_tokens: 512,
temperature: 0.7,
top_p: 0.95,
repetition_penalty: 1.1
},
use_fine_tune: (version as any).llm.use_fine_tune || false,
fine_tune_zip: (version as any).llm.fine_tune_zip || ''
}
});
}
// Clear and rebuild intents
this.intents.clear();
((version as any).intents || []).forEach((intent: any) => {
this.intents.push(this.createIntentFormGroup(intent));
});
this.isDirty = false;
}
async loadVersions() {
this.loading = true;
try {
const project = await this.apiService.getProject(this.project.id).toPromise();
if (project) {
this.project = project;
this.versions = [...project.versions].sort((a, b) => b.no - a.no);
// Re-select current version if it still exists
if (this.selectedVersion) {
const currentVersion = this.versions.find(v => v.no === this.selectedVersion!.no);
if (currentVersion) {
this.loadVersion(currentVersion);
} else if (this.versions.length > 0) {
this.loadVersion(this.versions[0]);
}
} else if (this.versions.length > 0) {
this.loadVersion(this.versions[0]);
}
}
} catch (error) {
this.snackBar.open('Failed to reload versions', 'Close', { duration: 3000 });
} finally {
this.loading = false;
}
}
createIntentFormGroup(intent: any = {}): FormGroup {
const group = this.fb.group({
name: [intent.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9-]+$/)]],
caption: [intent.caption || ''],
detection_prompt: [intent.detection_prompt || '', Validators.required],
examples: [intent.examples || []], // Store as array, not FormArray
parameters: this.fb.array([]),
action: [intent.action || '', Validators.required],
fallback_timeout_prompt: [intent.fallback_timeout_prompt || ''],
fallback_error_prompt: [intent.fallback_error_prompt || '']
});
// Parameters'ı ayrı olarak ekle
if (intent.parameters && Array.isArray(intent.parameters)) {
const parametersArray = group.get('parameters') as FormArray;
intent.parameters.forEach((param: any) => {
parametersArray.push(this.createParameterFormGroup(param));
});
}
return group;
}
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;
}
// LocalizedExample support methods
getLocalizedExamples(examples: any[], locale: string): LocalizedExample[] {
if (!examples || !Array.isArray(examples)) return [];
// Check if examples are in new format
if (examples.length > 0 && typeof examples[0] === 'object' && 'locale_code' in examples[0]) {
return examples.filter(ex => ex.locale_code === locale);
}
// Old format - convert to new
if (typeof examples[0] === 'string') {
return examples.map(ex => ({ locale_code: locale, example: ex }));
}
return [];
}
getParameterCaptionDisplay(captions: LocalizedCaption[]): string {
if (!captions || !Array.isArray(captions) || captions.length === 0) {
return '(No caption)';
}
// Try to find caption for selected locale
const selectedCaption = captions.find(c => c.locale_code === this.selectedExampleLocale);
if (selectedCaption) return selectedCaption.caption;
// Try default locale
const defaultCaption = captions.find(c => c.locale_code === this.project.default_locale);
if (defaultCaption) return defaultCaption.caption;
// Return first available caption
return captions[0].caption;
}
addLocalizedExample(intentIndex: number, example: string) {
if (!example.trim()) return;
const intent = this.intents.at(intentIndex);
const currentExamples = intent.get('examples')?.value || [];
// Check if already exists
const exists = currentExamples.some((ex: any) =>
ex.locale_code === this.selectedExampleLocale && ex.example === example.trim()
);
if (!exists) {
const newExamples = [...currentExamples, {
locale_code: this.selectedExampleLocale,
example: example.trim()
}];
intent.patchValue({ examples: newExamples });
this.isDirty = true;
}
}
removeLocalizedExample(intentIndex: number, exampleToRemove: LocalizedExample) {
const intent = this.intents.at(intentIndex);
const currentExamples = intent.get('examples')?.value || [];
const newExamples = currentExamples.filter((ex: any) =>
!(ex.locale_code === exampleToRemove.locale_code && ex.example === exampleToRemove.example)
);
intent.patchValue({ examples: newExamples });
this.isDirty = true;
}
addParameter(intentIndex: number) {
const parameters = this.getIntentParameters(intentIndex);
parameters.push(this.createParameterFormGroup());
this.isDirty = true;
}
removeParameter(intentIndex: number, paramIndex: number) {
const parameters = this.getIntentParameters(intentIndex);
parameters.removeAt(paramIndex);
this.isDirty = true;
}
// Check if version can be edited
get canEdit(): boolean {
const canEditResult = !this.selectedVersion?.published;
console.log('Can edit check:', 'Version:', this.selectedVersion?.no, 'Published:', this.selectedVersion?.published, 'Result:', canEditResult);
return canEditResult;
}
addIntent() {
this.intents.push(this.createIntentFormGroup());
this.isDirty = true;
}
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);
this.isDirty = true;
}
});
}
async editIntent(intentIndex: number) {
const { default: IntentEditDialogComponent } = await import('../intent-edit-dialog/intent-edit-dialog.component');
const intent = this.intents.at(intentIndex);
const currentValue = intent.value;
// Intent verilerini dialog'a gönder
const dialogRef = this.dialog.open(IntentEditDialogComponent, {
width: '90vw',
maxWidth: '1000px',
data: {
intent: {
...currentValue,
examples: currentValue.examples || [],
parameters: currentValue.parameters || []
},
project: this.project,
apis: await this.getAvailableAPIs()
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// Update intent with result
intent.patchValue({
name: result.name,
caption: result.caption,
detection_prompt: result.detection_prompt,
examples: result.examples || [],
action: result.action,
fallback_timeout_prompt: result.fallback_timeout_prompt,
fallback_error_prompt: result.fallback_error_prompt
});
// Update parameters
const parametersArray = intent.get('parameters') as FormArray;
parametersArray.clear();
(result.parameters || []).forEach((param: any) => {
parametersArray.push(this.createParameterFormGroup(param));
});
this.isDirty = true;
}
});
}
async getAvailableAPIs(): Promise<any[]> {
try {
return await this.apiService.getAPIs().toPromise() || [];
} catch {
return [];
}
}
async createVersion() {
const publishedVersions = this.versions.filter(v => v.published);
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '500px',
data: {
title: 'Create New Version',
message: 'Which published version would you like to use as a base for the new version?',
showDropdown: true,
dropdownOptions: publishedVersions.map(v => ({
value: v.no,
label: `Version ${v.no} - ${v.caption || 'No description'}`
})),
dropdownPlaceholder: 'Select published version (or leave empty for blank)',
confirmText: 'Create',
cancelText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(async (result) => {
if (result?.confirmed) {
this.creating = true;
try {
let newVersionData;
if (result.selectedValue) {
// Copy from selected version - we need to get the full version data
const sourceVersion = this.versions.find(v => v.no === result.selectedValue);
if (sourceVersion) {
// Load the full version data from the current form if it's the selected version
if (sourceVersion.no === this.selectedVersion?.no) {
const formValue = this.versionForm.getRawValue();
newVersionData = {
...formValue,
no: undefined,
published: false,
last_update_date: undefined,
caption: `Copy of ${sourceVersion.caption || `version ${sourceVersion.no}`}`
};
} else {
// For other versions, we only have basic info, so create minimal copy
newVersionData = {
caption: `Copy of ${sourceVersion.caption || `version ${sourceVersion.no}`}`,
general_prompt: '',
llm: {
repo_id: '',
generation_config: {
max_new_tokens: 512,
temperature: 0.7,
top_p: 0.95,
repetition_penalty: 1.1
},
use_fine_tune: false,
fine_tune_zip: ''
},
intents: []
};
}
}
} else {
// Create blank version
newVersionData = {
caption: `Version ${this.versions.length + 1}`,
general_prompt: '',
llm: {
repo_id: '',
generation_config: {
max_new_tokens: 512,
temperature: 0.7,
top_p: 0.95,
repetition_penalty: 1.1
},
use_fine_tune: false,
fine_tune_zip: ''
},
intents: []
};
}
if (newVersionData) {
await this.apiService.createVersion(this.project.id, newVersionData).toPromise();
await this.loadVersions();
this.snackBar.open('Version created successfully!', 'Close', { duration: 3000 });
}
} catch (error) {
this.snackBar.open('Failed to create version', 'Close', { duration: 3000 });
} finally {
this.creating = false;
}
}
});
}
async saveVersion() {
console.log('Save button clicked - canEdit:', this.canEdit, 'published:', this.selectedVersion?.published);
if (!this.selectedVersion || !this.canEdit) {
this.snackBar.open('Cannot save published version', 'Close', { duration: 3000 });
return;
}
if (this.versionForm.invalid) {
const invalidFields: string[] = [];
Object.keys(this.versionForm.controls).forEach(key => {
const control = this.versionForm.get(key);
if (control && control.invalid) {
invalidFields.push(key);
}
});
this.intents.controls.forEach((intent, index) => {
if (intent.invalid) {
invalidFields.push(`Intent ${index + 1}`);
}
});
this.snackBar.open(`Please fix validation errors in: ${invalidFields.join(', ')}`, 'Close', {
duration: 5000
});
return;
}
const currentVersion = this.selectedVersion!;
this.saving = true;
try {
const formValue = this.versionForm.getRawValue();
// updateData'yı backend'in beklediği formatta hazırla
const updateData = {
caption: formValue.caption,
general_prompt: formValue.general_prompt || '',
welcome_prompt: formValue.welcome_prompt || '', // Added welcome_prompt
llm: formValue.llm,
intents: formValue.intents.map((intent: any) => ({
name: intent.name,
caption: intent.caption,
detection_prompt: intent.detection_prompt,
examples: Array.isArray(intent.examples) ? intent.examples : [],
parameters: Array.isArray(intent.parameters) ? intent.parameters.map((param: any) => ({
name: param.name,
caption: param.caption,
type: param.type,
required: param.required,
variable_name: param.variable_name,
extraction_prompt: param.extraction_prompt,
validation_regex: param.validation_regex,
invalid_prompt: param.invalid_prompt,
type_error_prompt: param.type_error_prompt
})) : [],
action: intent.action,
fallback_timeout_prompt: intent.fallback_timeout_prompt,
fallback_error_prompt: intent.fallback_error_prompt
})),
last_update_date: currentVersion.last_update_date || ''
};
console.log('Saving version data:', JSON.stringify(updateData, null, 2));
const result = await this.apiService.updateVersion(
this.project.id,
currentVersion.no,
updateData
).toPromise();
this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 });
this.isDirty = false;
if (result) {
this.selectedVersion = result;
this.versionForm.patchValue({
last_update_date: result.last_update_date
});
}
await this.loadVersions();
} catch (error: any) {
console.error('Save error:', error);
if (error.status === 409) {
// Race condition handling
await this.handleRaceCondition(currentVersion);
} else if (error.status === 400 && error.error?.detail?.includes('Published versions')) {
this.snackBar.open('Published versions cannot be modified. Create a new version instead.', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
} else {
const errorMessage = error.error?.detail || error.message || 'Failed to save version';
this.snackBar.open(errorMessage, 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
}
} finally {
this.saving = false;
}
}
// Race condition handling
private async handleRaceCondition(currentVersion: Version) {
const formValue = this.versionForm.getRawValue();
const retryUpdateData = {
caption: formValue.caption,
general_prompt: formValue.general_prompt || '',
welcome_prompt: formValue.welcome_prompt || '',
llm: formValue.llm,
intents: formValue.intents.map((intent: any) => ({
name: intent.name,
caption: intent.caption,
detection_prompt: intent.detection_prompt,
examples: Array.isArray(intent.examples) ? intent.examples : [],
parameters: Array.isArray(intent.parameters) ? intent.parameters : [],
action: intent.action,
fallback_timeout_prompt: intent.fallback_timeout_prompt,
fallback_error_prompt: intent.fallback_error_prompt
})),
last_update_date: currentVersion.last_update_date || ''
};
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 force save?',
confirmText: 'Force Save',
cancelText: 'Reload',
confirmColor: 'warn'
}
});
dialogRef.afterClosed().subscribe(async (forceSave) => {
if (forceSave) {
try {
await this.apiService.updateVersion(
this.project.id,
currentVersion.no,
retryUpdateData,
true
).toPromise();
this.snackBar.open('Version force saved', 'Close', { duration: 3000 });
await this.loadVersions();
} catch (err: any) {
this.snackBar.open(err.error?.detail || 'Force save failed', 'Close', {
duration: 5000,
panelClass: 'error-snackbar'
});
}
} else {
await this.loadVersions();
}
});
}
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.no
).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.no
).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) {
// Check examples in all locales
const allExamples = intent.examples || [];
for (const example of allExamples) {
const exampleText = typeof example === 'string' ? example : example.example;
if (this.testUserMessage.toLowerCase().includes(exampleText.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
}));
}
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.no - a.no);
}
} catch (error) {
console.error('Failed to reload project:', error);
} finally {
this.loading = false;
}
}
async compareVersions() {
if (this.versions.length < 2) {
this.snackBar.open('Need at least 2 versions to compare', 'Close', { duration: 3000 });
return;
}
const { default: VersionCompareDialogComponent } = await import('../version-compare-dialog/version-compare-dialog.component');
this.dialog.open(VersionCompareDialogComponent, {
width: '90vw',
maxWidth: '1000px',
maxHeight: '80vh',
data: {
versions: this.versions,
selectedVersion: this.selectedVersion
}
});
}
close() {
if (this.isDirty) {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '400px',
data: {
title: 'Unsaved Changes',
message: 'You have unsaved changes. Do you want to save before closing?',
confirmText: 'Save & Close',
cancelText: 'Discard Changes',
showThirdOption: true,
thirdOptionText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result === 'confirm') {
this.saveVersion();
this.dialogRef.close();
} else if (result === 'cancel') {
this.dialogRef.close();
}
// If result is null or 'third', don't close
});
} else {
this.dialogRef.close();
}
}
}