Spaces:
Building
Building
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; | |
} | |
({ | |
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>, | |
public data: any (MAT_DIALOG_DATA) | |
) { | |
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(); | |
} | |
} | |
} |