ciyidogan commited on
Commit
14e2599
·
verified ·
1 Parent(s): 74d4ba8

Update flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.ts

Browse files
flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.ts CHANGED
@@ -16,7 +16,9 @@ import { MatExpansionModule } from '@angular/material/expansion';
16
  import { MatDividerModule } from '@angular/material/divider';
17
  import { MatProgressBarModule } from '@angular/material/progress-bar';
18
  import { MatListModule } from '@angular/material/list';
 
19
  import { ApiService, Project, Version } from '../../services/api.service';
 
20
  import ConfirmDialogComponent from '../confirm-dialog/confirm-dialog.component';
21
 
22
  // Interfaces for multi-language support
@@ -25,6 +27,16 @@ interface LocalizedExample {
25
  example: string;
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
28
  @Component({
29
  selector: 'app-version-edit-dialog',
30
  standalone: true,
@@ -46,7 +58,8 @@ interface LocalizedExample {
46
  MatExpansionModule,
47
  MatDividerModule,
48
  MatProgressBarModule,
49
- MatListModule
 
50
  ],
51
  templateUrl: './version-edit-dialog.component.html',
52
  styleUrls: ['./version-edit-dialog.component.scss']
@@ -56,183 +69,186 @@ export default class VersionEditDialogComponent implements OnInit {
56
  versions: Version[] = [];
57
  selectedVersion: Version | null = null;
58
  versionForm!: FormGroup;
59
-
60
  loading = false;
61
  saving = false;
62
  publishing = false;
63
- creating = false;
64
- isDirty = false;
65
-
66
- selectedTabIndex = 0;
67
- testUserMessage = '';
68
- testResult: any = null;
69
  testing = false;
 
 
 
70
 
71
- // Locale for examples
72
  selectedExampleLocale: string = 'tr';
 
73
 
74
  constructor(
75
  private fb: FormBuilder,
76
- private apiService: ApiService,
 
77
  private snackBar: MatSnackBar,
78
  private dialog: MatDialog,
79
  public dialogRef: MatDialogRef<VersionEditDialogComponent>,
80
- @Inject(MAT_DIALOG_DATA) public data: any
81
  ) {
82
  this.project = data.project;
83
- this.versions = [...this.project.versions].sort((a, b) => b.no - a.no);
84
  this.selectedExampleLocale = this.project.default_locale || 'tr';
85
  }
86
 
87
  ngOnInit() {
88
  this.initializeForm();
89
-
90
- // Select the latest unpublished version or the latest version
91
- const unpublished = this.versions.find(v => !v.published);
92
- this.selectedVersion = unpublished || this.versions[0] || null;
93
-
94
- if (this.selectedVersion) {
95
- this.loadVersion(this.selectedVersion);
96
- }
97
-
98
- this.versionForm.valueChanges.subscribe(() => {
99
- this.isDirty = true;
100
- });
101
  }
102
 
103
  initializeForm() {
104
  this.versionForm = this.fb.group({
105
- no: [{value: '', disabled: true}],
106
- caption: ['', Validators.required],
107
- published: [{value: false, disabled: true}],
108
- general_prompt: ['', Validators.required],
109
- llm: this.fb.group({
110
- repo_id: ['', Validators.required],
111
- generation_config: this.fb.group({
112
- max_new_tokens: [256, [Validators.required, Validators.min(1), Validators.max(2048)]],
113
- temperature: [0.2, [Validators.required, Validators.min(0), Validators.max(2)]],
114
- top_p: [0.8, [Validators.required, Validators.min(0), Validators.max(1)]],
115
- repetition_penalty: [1.1, [Validators.required, Validators.min(1), Validators.max(2)]]
116
- }),
117
- use_fine_tune: [false],
118
- fine_tune_zip: ['']
119
  }),
120
- intents: this.fb.array([]),
121
- last_update_date: ['']
122
  });
123
 
124
- // Watch for fine-tune toggle
125
- this.versionForm.get('llm.use_fine_tune')?.valueChanges.subscribe(useFineTune => {
126
- const fineTuneControl = this.versionForm.get('llm.fine_tune_zip');
127
- if (useFineTune) {
128
- fineTuneControl?.setValidators([Validators.required]);
129
- } else {
130
- fineTuneControl?.clearValidators();
131
- fineTuneControl?.setValue('');
132
- }
133
- fineTuneControl?.updateValueAndValidity();
134
  });
135
  }
136
 
137
- loadVersion(version: Version) {
138
- this.selectedVersion = version;
139
-
140
- // Form değerlerini set et
141
- this.versionForm.patchValue({
142
- no: version.no,
143
- caption: version.caption || '',
144
- published: version.published || false,
145
- general_prompt: (version as any).general_prompt || '',
146
- last_update_date: version.last_update_date || ''
147
- });
148
-
149
- // LLM config'i ayrı set et
150
- if (version.llm) {
151
- this.versionForm.patchValue({
152
- llm: {
153
- repo_id: version.llm.repo_id || '',
154
- generation_config: version.llm.generation_config || {
155
- max_new_tokens: 512,
156
- temperature: 0.7,
157
- top_p: 0.95,
158
- repetition_penalty: 1.1
159
- },
160
- use_fine_tune: version.llm.use_fine_tune || false,
161
- fine_tune_zip: version.llm.fine_tune_zip || ''
162
- }
163
- });
164
  }
165
-
166
- // Clear and rebuild intents
167
- this.intents.clear();
168
- (version.intents || []).forEach(intent => {
169
- this.intents.push(this.createIntentFormGroup(intent));
170
- });
171
 
172
- this.isDirty = false;
 
173
  }
174
-
175
- async loadVersions() {
 
 
 
 
 
176
  this.loading = true;
177
- try {
178
- const project = await this.apiService.getProject(this.project.id).toPromise();
179
- if (project) {
180
- this.project = project;
181
- this.versions = [...project.versions].sort((a, b) => b.no - a.no);
182
 
183
- // Re-select current version if it still exists
184
- if (this.selectedVersion) {
185
- const currentVersion = this.versions.find(v => v.no === this.selectedVersion!.no);
186
- if (currentVersion) {
187
- this.loadVersion(currentVersion);
188
- } else if (this.versions.length > 0) {
189
- this.loadVersion(this.versions[0]);
190
- }
191
- } else if (this.versions.length > 0) {
192
- this.loadVersion(this.versions[0]);
193
  }
 
 
 
 
 
194
  }
195
- } catch (error) {
196
- this.snackBar.open('Failed to reload versions', 'Close', { duration: 3000 });
197
- } finally {
198
- this.loading = false;
199
- }
200
  }
201
 
202
- createIntentFormGroup(intent: any = {}): FormGroup {
203
- const group = this.fb.group({
204
- name: [intent.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9-]+$/)]],
205
- caption: [intent.caption || ''],
206
- detection_prompt: [intent.detection_prompt || '', Validators.required],
207
- examples: [intent.examples || []], // Store as array, not FormArray
208
- parameters: this.fb.array([]),
209
- action: [intent.action || '', Validators.required],
210
- fallback_timeout_prompt: [intent.fallback_timeout_prompt || ''],
211
- fallback_error_prompt: [intent.fallback_error_prompt || '']
 
 
 
 
 
 
 
212
  });
213
-
214
- // Parameters'ı ayrı olarak ekle
215
- if (intent.parameters && Array.isArray(intent.parameters)) {
216
- const parametersArray = group.get('parameters') as FormArray;
217
- intent.parameters.forEach((param: any) => {
218
- parametersArray.push(this.createParameterFormGroup(param));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  });
220
  }
221
-
222
- return group;
223
  }
224
 
225
- createParameterFormGroup(param: any = {}): FormGroup {
226
  return this.fb.group({
227
- name: [param.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]],
228
- caption: [param.caption || ''],
229
- type: [param.type || 'str', Validators.required],
230
- required: [param.required !== false],
231
- variable_name: [param.variable_name || '', Validators.required],
232
- extraction_prompt: [param.extraction_prompt || ''],
233
- validation_regex: [param.validation_regex || ''],
234
- invalid_prompt: [param.invalid_prompt || ''],
235
- type_error_prompt: [param.type_error_prompt || '']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  });
237
  }
238
 
@@ -261,6 +277,23 @@ export default class VersionEditDialogComponent implements OnInit {
261
  return [];
262
  }
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  addLocalizedExample(intentIndex: number, example: string) {
265
  if (!example.trim()) return;
266
 
@@ -294,6 +327,18 @@ export default class VersionEditDialogComponent implements OnInit {
294
  this.isDirty = true;
295
  }
296
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  // Check if version can be edited
298
  get canEdit(): boolean {
299
  return !this.selectedVersion?.published;
@@ -301,6 +346,7 @@ export default class VersionEditDialogComponent implements OnInit {
301
 
302
  addIntent() {
303
  this.intents.push(this.createIntentFormGroup());
 
304
  }
305
 
306
  removeIntent(index: number) {
@@ -318,6 +364,7 @@ export default class VersionEditDialogComponent implements OnInit {
318
  dialogRef.afterClosed().subscribe(confirmed => {
319
  if (confirmed) {
320
  this.intents.removeAt(index);
 
321
  }
322
  });
323
  }
@@ -368,14 +415,8 @@ export default class VersionEditDialogComponent implements OnInit {
368
  });
369
  }
370
 
371
- addParameter(intentIndex: number) {
372
- const parameters = this.getIntentParameters(intentIndex);
373
- parameters.push(this.createParameterFormGroup());
374
- }
375
-
376
- removeParameter(intentIndex: number, paramIndex: number) {
377
- const parameters = this.getIntentParameters(intentIndex);
378
- parameters.removeAt(paramIndex);
379
  }
380
 
381
  async createVersion() {
@@ -407,45 +448,42 @@ export default class VersionEditDialogComponent implements OnInit {
407
  // Copy from selected version
408
  const sourceVersion = this.versions.find(v => v.no === result.selectedValue);
409
  if (sourceVersion) {
 
410
  newVersionData = {
411
- ...sourceVersion,
412
  no: undefined,
413
  published: false,
414
  last_update_date: undefined,
415
- caption: `Copy of version ${sourceVersion.no}`,
416
- description: sourceVersion.caption ? `Copy of ${sourceVersion.caption}` : `Copy of version ${sourceVersion.no}`
417
  };
418
  }
419
  } else {
420
  // Create blank version
421
  newVersionData = {
422
- caption: `Version ${this.versions.length + 1}`,
423
- description: 'New version',
424
- default_api: '',
425
- published: false,
426
- llm: {
427
- repo_id: '',
428
- generation_config: {
429
- max_new_tokens: 512,
430
- temperature: 0.7,
431
- top_p: 0.95,
432
- top_k: 50,
433
- repetition_penalty: 1.1
434
- },
435
- use_fine_tune: false,
436
- fine_tune_zip: ''
437
  },
438
- intents: [],
439
- parameters: []
440
  };
441
  }
442
-
443
- if (newVersionData) {
444
- await this.apiService.createVersion(this.project.id, newVersionData).toPromise();
445
- await this.loadVersions();
446
- this.snackBar.open('Version created successfully!', 'Close', { duration: 3000 });
447
- }
 
 
 
448
  } catch (error) {
 
449
  this.snackBar.open('Failed to create version', 'Close', { duration: 3000 });
450
  } finally {
451
  this.loading = false;
@@ -454,355 +492,209 @@ export default class VersionEditDialogComponent implements OnInit {
454
  });
455
  }
456
 
457
- async saveVersion() {
458
- if (!this.selectedVersion || !this.canEdit) {
459
- this.snackBar.open('Cannot save published version', 'Close', { duration: 3000 });
460
- return;
461
- }
462
-
463
- if (this.versionForm.invalid) {
464
- const invalidFields: string[] = [];
465
- Object.keys(this.versionForm.controls).forEach(key => {
466
- const control = this.versionForm.get(key);
467
- if (control && control.invalid) {
468
- invalidFields.push(key);
469
- }
470
- });
471
-
472
- this.intents.controls.forEach((intent, index) => {
473
- if (intent.invalid) {
474
- invalidFields.push(`Intent ${index + 1}`);
475
- }
476
- });
477
-
478
- this.snackBar.open(`Please fix validation errors in: ${invalidFields.join(', ')}`, 'Close', {
479
- duration: 5000
480
- });
481
- return;
482
- }
483
-
484
- const currentVersion = this.selectedVersion!;
485
-
486
- this.saving = true;
487
-
488
- try {
489
- const formValue = this.versionForm.getRawValue();
490
-
491
- // updateData'yı backend'in beklediği formatta hazırla
492
- const updateData = {
493
- caption: formValue.caption,
494
- general_prompt: formValue.general_prompt || '',
495
- llm: formValue.llm,
496
- intents: formValue.intents.map((intent: any) => ({
497
- name: intent.name,
498
- caption: intent.caption,
499
- detection_prompt: intent.detection_prompt,
500
- examples: Array.isArray(intent.examples) ? intent.examples : [],
501
- parameters: Array.isArray(intent.parameters) ? intent.parameters.map((param: any) => ({
502
- name: param.name,
503
- caption: param.caption,
504
- type: param.type,
505
- required: param.required,
506
- variable_name: param.variable_name,
507
- extraction_prompt: param.extraction_prompt,
508
- validation_regex: param.validation_regex,
509
- invalid_prompt: param.invalid_prompt,
510
- type_error_prompt: param.type_error_prompt
511
- })) : [],
512
- action: intent.action,
513
- fallback_timeout_prompt: intent.fallback_timeout_prompt,
514
- fallback_error_prompt: intent.fallback_error_prompt
515
- })),
516
- last_update_date: currentVersion.last_update_date || ''
517
- };
518
-
519
- console.log('Saving version data:', JSON.stringify(updateData, null, 2));
520
-
521
- const result = await this.apiService.updateVersion(
522
- this.project.id,
523
- currentVersion.no,
524
- updateData
525
- ).toPromise();
526
-
527
- this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 });
528
-
529
- this.isDirty = false;
530
-
531
- if (result) {
532
- this.selectedVersion = result;
533
- this.versionForm.patchValue({
534
- last_update_date: result.last_update_date
535
- });
536
- }
537
-
538
- await this.loadVersions();
539
-
540
- } catch (error: any) {
541
- console.error('Save error:', error);
542
-
543
- if (error.status === 409) {
544
- // Race condition handling
545
- await this.handleRaceCondition(currentVersion);
546
- } else if (error.status === 400 && error.error?.detail?.includes('Published versions')) {
547
- this.snackBar.open('Published versions cannot be modified. Create a new version instead.', 'Close', {
548
- duration: 5000,
549
- panelClass: 'error-snackbar'
550
- });
551
- } else {
552
- const errorMessage = error.error?.detail || error.message || 'Failed to save version';
553
- this.snackBar.open(errorMessage, 'Close', {
554
- duration: 5000,
555
- panelClass: 'error-snackbar'
556
- });
557
- }
558
- } finally {
559
- this.saving = false;
560
- }
561
- }
562
-
563
- // Race condition handling
564
- private async handleRaceCondition(currentVersion: Version) {
565
- const formValue = this.versionForm.getRawValue();
566
-
567
- const retryUpdateData = {
568
- caption: formValue.caption,
569
- general_prompt: formValue.general_prompt || '',
570
- llm: formValue.llm,
571
- intents: formValue.intents.map((intent: any) => ({
572
- name: intent.name,
573
- caption: intent.caption,
574
- detection_prompt: intent.detection_prompt,
575
- examples: Array.isArray(intent.examples) ? intent.examples : [],
576
- parameters: Array.isArray(intent.parameters) ? intent.parameters : [],
577
- action: intent.action,
578
- fallback_timeout_prompt: intent.fallback_timeout_prompt,
579
- fallback_error_prompt: intent.fallback_error_prompt
580
- })),
581
- last_update_date: currentVersion.last_update_date || ''
582
- };
583
-
584
- const dialogRef = this.dialog.open(ConfirmDialogComponent, {
585
- width: '500px',
586
- data: {
587
- title: 'Version Modified',
588
- message: 'This version was modified by another user. Do you want to reload and lose your changes, or force save?',
589
- confirmText: 'Force Save',
590
- cancelText: 'Reload',
591
- confirmColor: 'warn'
592
- }
593
- });
594
-
595
- dialogRef.afterClosed().subscribe(async (forceSave) => {
596
- if (forceSave) {
597
- try {
598
- await this.apiService.updateVersion(
599
- this.project.id,
600
- currentVersion.no,
601
- retryUpdateData,
602
- true
603
- ).toPromise();
604
- this.snackBar.open('Version force saved', 'Close', { duration: 3000 });
605
- await this.loadVersions();
606
- } catch (err: any) {
607
- this.snackBar.open(err.error?.detail || 'Force save failed', 'Close', {
608
- duration: 5000,
609
- panelClass: 'error-snackbar'
610
- });
611
- }
612
- } else {
613
- await this.loadVersions();
614
- }
615
- });
616
- }
617
-
618
- async publishVersion() {
619
  if (!this.selectedVersion) return;
620
-
621
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
622
- width: '500px',
623
  data: {
624
- title: 'Publish Version',
625
- message: `Are you sure you want to publish version "${this.selectedVersion.caption}"? This will unpublish all other versions.`,
626
- confirmText: 'Publish',
627
- confirmColor: 'primary'
628
  }
629
  });
630
 
631
  dialogRef.afterClosed().subscribe(async (confirmed) => {
632
- if (confirmed && this.selectedVersion) {
633
- this.publishing = true;
634
  try {
635
- await this.apiService.publishVersion(
636
- this.project.id,
637
- this.selectedVersion.no
638
- ).toPromise();
639
-
640
- this.snackBar.open('Version published successfully', 'Close', { duration: 3000 });
 
 
641
 
642
- // Reload to get updated data
643
- await this.reloadProject();
 
 
644
 
645
- } catch (error: any) {
646
- this.snackBar.open(error.error?.detail || 'Failed to publish version', 'Close', {
647
- duration: 5000,
648
- panelClass: 'error-snackbar'
649
- });
650
  } finally {
651
- this.publishing = false;
652
  }
653
  }
654
  });
655
  }
656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  async deleteVersion() {
658
  if (!this.selectedVersion || this.selectedVersion.published) return;
659
-
660
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
661
  width: '400px',
662
  data: {
663
  title: 'Delete Version',
664
- message: `Are you sure you want to delete version "${this.selectedVersion.caption}"?`,
665
  confirmText: 'Delete',
666
  confirmColor: 'warn'
667
  }
668
  });
669
 
670
  dialogRef.afterClosed().subscribe(async (confirmed) => {
671
- if (confirmed && this.selectedVersion) {
 
672
  try {
673
- await this.apiService.deleteVersion(
674
- this.project.id,
675
- this.selectedVersion.no
676
- ).toPromise();
677
-
678
- this.snackBar.open('Version deleted successfully', 'Close', { duration: 3000 });
679
 
680
- // Reload and select another version
681
- await this.reloadProject();
682
-
683
- if (this.versions.length > 0) {
684
- this.loadVersion(this.versions[0]);
685
- } else {
686
- this.selectedVersion = null;
687
- }
688
 
689
- } catch (error: any) {
690
- this.snackBar.open(error.error?.detail || 'Failed to delete version', 'Close', {
691
- duration: 5000,
692
- panelClass: 'error-snackbar'
693
- });
 
694
  }
695
  }
696
  });
697
  }
698
 
699
- async testIntentDetection() {
700
- if (!this.testUserMessage.trim()) {
701
- this.snackBar.open('Please enter a test message', 'Close', { duration: 3000 });
702
- return;
703
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  this.testing = true;
706
  this.testResult = null;
707
-
708
  // Simulate intent detection test
709
  setTimeout(() => {
710
- // This is a mock - in real implementation, this would call the Spark service
711
- const intents = this.versionForm.get('intents')?.value || [];
712
 
713
- // Simple matching for demo
714
- let detectedIntent = null;
715
- let confidence = 0;
716
-
717
- for (const intent of intents) {
718
- // Check examples in all locales
719
- const allExamples = intent.examples || [];
720
- for (const example of allExamples) {
721
- const exampleText = typeof example === 'string' ? example : example.example;
722
- if (this.testUserMessage.toLowerCase().includes(exampleText.toLowerCase())) {
723
- detectedIntent = intent.name;
724
- confidence = 0.95;
725
- break;
726
- }
727
- }
728
- if (detectedIntent) break;
729
- }
730
-
731
- // Random detection for demo
732
- if (!detectedIntent && intents.length > 0) {
733
- const randomIntent = intents[Math.floor(Math.random() * intents.length)];
734
- detectedIntent = randomIntent.name;
735
- confidence = 0.65;
736
  }
737
-
738
- this.testResult = {
739
- success: true,
740
- intent: detectedIntent,
741
- confidence: confidence,
742
- parameters: detectedIntent ? this.extractTestParameters(detectedIntent) : []
743
- };
744
-
745
  this.testing = false;
746
  }, 1500);
747
  }
748
 
749
- private extractTestParameters(intentName: string): any[] {
750
- // Mock parameter extraction
751
- const intent = this.intents.value.find((i: any) => i.name === intentName);
752
- if (!intent) return [];
753
-
754
- return intent.parameters.map((param: any) => ({
755
- name: param.name,
756
- value: param.type === 'date' ? '2025-06-15' : 'test_value',
757
- extracted: Math.random() > 0.3
758
- }));
759
- }
760
-
761
- async getAvailableAPIs(): Promise<any[]> {
762
- try {
763
- return await this.apiService.getAPIs().toPromise() || [];
764
- } catch {
765
- return [];
766
- }
767
- }
768
-
769
- private async reloadProject() {
770
- this.loading = true;
771
- try {
772
- const projects = await this.apiService.getProjects().toPromise() || [];
773
- const updatedProject = projects.find(p => p.id === this.project.id);
774
-
775
- if (updatedProject) {
776
- this.project = updatedProject;
777
- this.versions = [...updatedProject.versions].sort((a, b) => b.no - a.no);
778
- }
779
- } catch (error) {
780
- console.error('Failed to reload project:', error);
781
- } finally {
782
- this.loading = false;
783
- }
784
- }
785
 
786
- async compareVersions() {
787
- if (this.versions.length < 2) {
788
- this.snackBar.open('Need at least 2 versions to compare', 'Close', { duration: 3000 });
789
- return;
 
 
 
 
 
 
 
790
  }
791
-
792
- const { default: VersionCompareDialogComponent } = await import('../version-compare-dialog/version-compare-dialog.component');
793
-
794
- this.dialog.open(VersionCompareDialogComponent, {
795
- width: '90vw',
796
- maxWidth: '1000px',
797
- maxHeight: '80vh',
798
- data: {
799
- versions: this.versions,
800
- selectedVersion: this.selectedVersion
801
- }
802
- });
803
- }
804
-
805
- close() {
806
- this.dialogRef.close(true);
807
  }
808
  }
 
16
  import { MatDividerModule } from '@angular/material/divider';
17
  import { MatProgressBarModule } from '@angular/material/progress-bar';
18
  import { MatListModule } from '@angular/material/list';
19
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
20
  import { ApiService, Project, Version } from '../../services/api.service';
21
+ import { LocaleManagerService } from '../../services/locale-manager.service';
22
  import ConfirmDialogComponent from '../confirm-dialog/confirm-dialog.component';
23
 
24
  // Interfaces for multi-language support
 
27
  example: string;
28
  }
29
 
30
+ interface LocalizedCaption {
31
+ locale_code: string;
32
+ caption: string;
33
+ }
34
+
35
+ interface Locale {
36
+ code: string;
37
+ name: string;
38
+ }
39
+
40
  @Component({
41
  selector: 'app-version-edit-dialog',
42
  standalone: true,
 
58
  MatExpansionModule,
59
  MatDividerModule,
60
  MatProgressBarModule,
61
+ MatListModule,
62
+ MatProgressSpinnerModule
63
  ],
64
  templateUrl: './version-edit-dialog.component.html',
65
  styleUrls: ['./version-edit-dialog.component.scss']
 
69
  versions: Version[] = [];
70
  selectedVersion: Version | null = null;
71
  versionForm!: FormGroup;
 
72
  loading = false;
73
  saving = false;
74
  publishing = false;
 
 
 
 
 
 
75
  testing = false;
76
+ testInput = '';
77
+ testResult: any = null;
78
+ isDirty = false;
79
 
80
+ // Multi-language support
81
  selectedExampleLocale: string = 'tr';
82
+ availableLocales: Locale[] = [];
83
 
84
  constructor(
85
  private fb: FormBuilder,
86
+ private api: ApiService,
87
+ private localeService: LocaleManagerService,
88
  private snackBar: MatSnackBar,
89
  private dialog: MatDialog,
90
  public dialogRef: MatDialogRef<VersionEditDialogComponent>,
91
+ @Inject(MAT_DIALOG_DATA) public data: { project: Project }
92
  ) {
93
  this.project = data.project;
 
94
  this.selectedExampleLocale = this.project.default_locale || 'tr';
95
  }
96
 
97
  ngOnInit() {
98
  this.initializeForm();
99
+ this.loadVersions();
100
+ this.loadAvailableLocales();
 
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
  initializeForm() {
104
  this.versionForm = this.fb.group({
105
+ caption: [''],
106
+ prompt: ['', Validators.required],
107
+ welcome_prompt: [''],
108
+ model: ['gpt-3.5-turbo'],
109
+ generation_config: this.fb.group({
110
+ max_new_tokens: [512],
111
+ temperature: [0.7],
112
+ top_p: [0.9],
113
+ top_k: [null],
114
+ do_sample: [true]
115
+ }),
116
+ fine_tune: this.fb.group({
117
+ enabled: [false],
118
+ adapter_path: ['']
119
  }),
120
+ intents: this.fb.array([])
 
121
  });
122
 
123
+ // Mark form as dirty on any change
124
+ this.versionForm.valueChanges.subscribe(() => {
125
+ this.isDirty = true;
 
 
 
 
 
 
 
126
  });
127
  }
128
 
129
+ async loadAvailableLocales() {
130
+ // Get supported locales from project
131
+ const supportedCodes = [
132
+ this.project.default_locale,
133
+ ...(this.project.supported_languages || [])
134
+ ].filter(Boolean);
135
+
136
+ // Get locale details
137
+ for (const code of supportedCodes) {
138
+ const localeInfo = await this.localeService.getLocaleDetails(code).toPromise();
139
+ if (localeInfo) {
140
+ this.availableLocales.push({
141
+ code: localeInfo.code,
142
+ name: localeInfo.name
143
+ });
144
+ }
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
+ }
 
 
 
 
 
147
 
148
+ getAvailableLocales(): Locale[] {
149
+ return this.availableLocales;
150
  }
151
+
152
+ getLocaleName(localeCode: string): string {
153
+ const locale = this.availableLocales.find(l => l.code === localeCode);
154
+ return locale?.name || localeCode;
155
+ }
156
+
157
+ loadVersions() {
158
  this.loading = true;
159
+ this.api.getProjectVersions(this.project.id).subscribe({
160
+ next: (versions) => {
161
+ this.versions = versions;
162
+ this.loading = false;
 
163
 
164
+ // Auto-select if only one version
165
+ if (versions.length === 1) {
166
+ this.onVersionChange(versions[0]);
 
 
 
 
 
 
 
167
  }
168
+ },
169
+ error: (error) => {
170
+ console.error('Error loading versions:', error);
171
+ this.snackBar.open('Failed to load versions', 'Close', { duration: 3000 });
172
+ this.loading = false;
173
  }
174
+ });
 
 
 
 
175
  }
176
 
177
+ onVersionChange(version: Version | null) {
178
+ if (!version) return;
179
+
180
+ this.selectedVersion = version;
181
+ this.loading = true;
182
+ this.isDirty = false;
183
+
184
+ this.api.getVersionDetails(this.project.id, version.no).subscribe({
185
+ next: (details) => {
186
+ this.populateForm(details);
187
+ this.loading = false;
188
+ },
189
+ error: (error) => {
190
+ console.error('Error loading version details:', error);
191
+ this.snackBar.open('Failed to load version details', 'Close', { duration: 3000 });
192
+ this.loading = false;
193
+ }
194
  });
195
+ }
196
+
197
+ populateForm(version: any) {
198
+ // Clear existing intents
199
+ const intentsArray = this.versionForm.get('intents') as FormArray;
200
+ while (intentsArray.length > 0) {
201
+ intentsArray.removeAt(0);
202
+ }
203
+
204
+ // Populate basic fields
205
+ this.versionForm.patchValue({
206
+ caption: version.caption || '',
207
+ prompt: version.prompt || '',
208
+ welcome_prompt: version.welcome_prompt || '',
209
+ model: version.model || 'gpt-3.5-turbo',
210
+ generation_config: version.generation_config || {},
211
+ fine_tune: version.fine_tune || { enabled: false }
212
+ });
213
+
214
+ // Add intents
215
+ if (version.intents && Array.isArray(version.intents)) {
216
+ version.intents.forEach((intent: any) => {
217
+ intentsArray.push(this.createIntentFormGroup(intent));
218
  });
219
  }
220
+
221
+ this.isDirty = false;
222
  }
223
 
224
+ createIntentFormGroup(intent?: any): FormGroup {
225
  return this.fb.group({
226
+ name: [intent?.name || '', Validators.required],
227
+ caption: [intent?.caption || ''],
228
+ requiresApproval: [intent?.requiresApproval || false],
229
+ dependencies: [intent?.dependencies || []],
230
+ examples: [intent?.examples || []],
231
+ detection_prompt: [intent?.detection_prompt || '', Validators.required],
232
+ parameters: this.fb.array(
233
+ (intent?.parameters || []).map((p: any) => this.createParameterFormGroup(p))
234
+ ),
235
+ action: [intent?.action || ''],
236
+ fallback_timeout_prompt: [intent?.fallback_timeout_prompt || ''],
237
+ fallback_error_prompt: [intent?.fallback_error_prompt || '']
238
+ });
239
+ }
240
+
241
+ createParameterFormGroup(param?: any): FormGroup {
242
+ return this.fb.group({
243
+ name: [param?.name || '', Validators.required],
244
+ caption: [param?.caption || []],
245
+ type: [param?.type || 'str', Validators.required],
246
+ required: [param?.required !== false],
247
+ variable_name: [param?.variable_name || '', Validators.required],
248
+ extraction_prompt: [param?.extraction_prompt || ''],
249
+ validation_regex: [param?.validation_regex || ''],
250
+ invalid_prompt: [param?.invalid_prompt || ''],
251
+ type_error_prompt: [param?.type_error_prompt || '']
252
  });
253
  }
254
 
 
277
  return [];
278
  }
279
 
280
+ getParameterCaptionDisplay(captions: LocalizedCaption[]): string {
281
+ if (!captions || !Array.isArray(captions) || captions.length === 0) {
282
+ return '(No caption)';
283
+ }
284
+
285
+ // Try to find caption for selected locale
286
+ const selectedCaption = captions.find(c => c.locale_code === this.selectedExampleLocale);
287
+ if (selectedCaption) return selectedCaption.caption;
288
+
289
+ // Try default locale
290
+ const defaultCaption = captions.find(c => c.locale_code === this.project.default_locale);
291
+ if (defaultCaption) return defaultCaption.caption;
292
+
293
+ // Return first available caption
294
+ return captions[0].caption;
295
+ }
296
+
297
  addLocalizedExample(intentIndex: number, example: string) {
298
  if (!example.trim()) return;
299
 
 
327
  this.isDirty = true;
328
  }
329
 
330
+ addParameter(intentIndex: number) {
331
+ const parameters = this.getIntentParameters(intentIndex);
332
+ parameters.push(this.createParameterFormGroup());
333
+ this.isDirty = true;
334
+ }
335
+
336
+ removeParameter(intentIndex: number, paramIndex: number) {
337
+ const parameters = this.getIntentParameters(intentIndex);
338
+ parameters.removeAt(paramIndex);
339
+ this.isDirty = true;
340
+ }
341
+
342
  // Check if version can be edited
343
  get canEdit(): boolean {
344
  return !this.selectedVersion?.published;
 
346
 
347
  addIntent() {
348
  this.intents.push(this.createIntentFormGroup());
349
+ this.isDirty = true;
350
  }
351
 
352
  removeIntent(index: number) {
 
364
  dialogRef.afterClosed().subscribe(confirmed => {
365
  if (confirmed) {
366
  this.intents.removeAt(index);
367
+ this.isDirty = true;
368
  }
369
  });
370
  }
 
415
  });
416
  }
417
 
418
+ async getAvailableAPIs() {
419
+ return this.api.getAPIs().toPromise();
 
 
 
 
 
 
420
  }
421
 
422
  async createVersion() {
 
448
  // Copy from selected version
449
  const sourceVersion = this.versions.find(v => v.no === result.selectedValue);
450
  if (sourceVersion) {
451
+ const details = await this.api.getVersionDetails(this.project.id, sourceVersion.no).toPromise();
452
  newVersionData = {
453
+ ...details,
454
  no: undefined,
455
  published: false,
456
  last_update_date: undefined,
457
+ caption: `Copy of version ${sourceVersion.no}`
 
458
  };
459
  }
460
  } else {
461
  // Create blank version
462
  newVersionData = {
463
+ caption: 'New Version',
464
+ prompt: '',
465
+ welcome_prompt: '',
466
+ model: 'gpt-3.5-turbo',
467
+ generation_config: {
468
+ max_new_tokens: 512,
469
+ temperature: 0.7,
470
+ top_p: 0.9,
471
+ do_sample: true
 
 
 
 
 
 
472
  },
473
+ intents: []
 
474
  };
475
  }
476
+
477
+ // Create new version
478
+ const newVersion = await this.api.createVersion(this.project.id, newVersionData).toPromise();
479
+
480
+ // Reload versions and select the new one
481
+ await this.loadVersions();
482
+ this.onVersionChange(newVersion);
483
+
484
+ this.snackBar.open('Version created successfully', 'Close', { duration: 3000 });
485
  } catch (error) {
486
+ console.error('Error creating version:', error);
487
  this.snackBar.open('Failed to create version', 'Close', { duration: 3000 });
488
  } finally {
489
  this.loading = false;
 
492
  });
493
  }
494
 
495
+ async copyVersion() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  if (!this.selectedVersion) return;
497
+
498
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
499
+ width: '400px',
500
  data: {
501
+ title: 'Copy Version',
502
+ message: `Create a copy of version ${this.selectedVersion.no}?`,
503
+ confirmText: 'Copy',
504
+ cancelText: 'Cancel'
505
  }
506
  });
507
 
508
  dialogRef.afterClosed().subscribe(async (confirmed) => {
509
+ if (confirmed) {
510
+ this.loading = true;
511
  try {
512
+ const details = await this.api.getVersionDetails(this.project.id, this.selectedVersion!.no).toPromise();
513
+ const copyData = {
514
+ ...details,
515
+ no: undefined,
516
+ published: false,
517
+ last_update_date: undefined,
518
+ caption: `Copy of version ${this.selectedVersion!.no}`
519
+ };
520
 
521
+ const newVersion = await this.api.createVersion(this.project.id, copyData).toPromise();
522
+
523
+ await this.loadVersions();
524
+ this.onVersionChange(newVersion);
525
 
526
+ this.snackBar.open('Version copied successfully', 'Close', { duration: 3000 });
527
+ } catch (error) {
528
+ console.error('Error copying version:', error);
529
+ this.snackBar.open('Failed to copy version', 'Close', { duration: 3000 });
 
530
  } finally {
531
+ this.loading = false;
532
  }
533
  }
534
  });
535
  }
536
 
537
+ async compareVersions() {
538
+ const { default: VersionCompareDialogComponent } = await import('../version-compare-dialog/version-compare-dialog.component');
539
+
540
+ this.dialog.open(VersionCompareDialogComponent, {
541
+ width: '90vw',
542
+ maxWidth: '1200px',
543
+ data: {
544
+ versions: this.versions,
545
+ selectedVersion: this.selectedVersion
546
+ }
547
+ });
548
+ }
549
+
550
  async deleteVersion() {
551
  if (!this.selectedVersion || this.selectedVersion.published) return;
552
+
553
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
554
  width: '400px',
555
  data: {
556
  title: 'Delete Version',
557
+ message: `Are you sure you want to delete version ${this.selectedVersion.no}? This action cannot be undone.`,
558
  confirmText: 'Delete',
559
  confirmColor: 'warn'
560
  }
561
  });
562
 
563
  dialogRef.afterClosed().subscribe(async (confirmed) => {
564
+ if (confirmed) {
565
+ this.loading = true;
566
  try {
567
+ await this.api.deleteVersion(this.project.id, this.selectedVersion!.no).toPromise();
 
 
 
 
 
568
 
569
+ this.selectedVersion = null;
570
+ await this.loadVersions();
 
 
 
 
 
 
571
 
572
+ this.snackBar.open('Version deleted successfully', 'Close', { duration: 3000 });
573
+ } catch (error) {
574
+ console.error('Error deleting version:', error);
575
+ this.snackBar.open('Failed to delete version', 'Close', { duration: 3000 });
576
+ } finally {
577
+ this.loading = false;
578
  }
579
  }
580
  });
581
  }
582
 
583
+ saveVersion() {
584
+ if (!this.selectedVersion || !this.canEdit || this.versionForm.invalid) return;
585
+
586
+ this.saving = true;
587
+ const formValue = this.versionForm.value;
588
+
589
+ this.api.updateVersion(this.project.id, this.selectedVersion.no, formValue).subscribe({
590
+ next: () => {
591
+ this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 });
592
+ this.isDirty = false;
593
+ this.saving = false;
594
+ },
595
+ error: (error) => {
596
+ console.error('Error saving version:', error);
597
+ this.snackBar.open('Failed to save version', 'Close', { duration: 3000 });
598
+ this.saving = false;
599
+ }
600
+ });
601
+ }
602
 
603
+ publishVersion() {
604
+ if (!this.selectedVersion || this.selectedVersion.published || this.isDirty) return;
605
+
606
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
607
+ width: '500px',
608
+ data: {
609
+ title: 'Publish Version',
610
+ message: `Publishing version ${this.selectedVersion.no} will make it the active version for all users. Published versions cannot be edited. Continue?`,
611
+ confirmText: 'Publish',
612
+ confirmColor: 'accent'
613
+ }
614
+ });
615
+
616
+ dialogRef.afterClosed().subscribe((confirmed) => {
617
+ if (confirmed) {
618
+ this.publishing = true;
619
+
620
+ this.api.publishVersion(this.project.id, this.selectedVersion!.no).subscribe({
621
+ next: () => {
622
+ this.snackBar.open('Version published successfully', 'Close', { duration: 3000 });
623
+ this.publishing = false;
624
+
625
+ // Reload to update status
626
+ this.loadVersions();
627
+ this.onVersionChange(this.selectedVersion);
628
+ },
629
+ error: (error) => {
630
+ console.error('Error publishing version:', error);
631
+ this.snackBar.open('Failed to publish version', 'Close', { duration: 3000 });
632
+ this.publishing = false;
633
+ }
634
+ });
635
+ }
636
+ });
637
+ }
638
+
639
+ testIntentDetection() {
640
+ if (!this.testInput || !this.selectedVersion) return;
641
+
642
  this.testing = true;
643
  this.testResult = null;
644
+
645
  // Simulate intent detection test
646
  setTimeout(() => {
647
+ const intents = this.intents.value;
 
648
 
649
+ // Simple simulation - in real app, this would call the backend
650
+ const randomIntent = intents[Math.floor(Math.random() * intents.length)];
651
+
652
+ if (randomIntent && Math.random() > 0.3) {
653
+ this.testResult = {
654
+ intent: randomIntent.name,
655
+ confidence: 0.75 + Math.random() * 0.20,
656
+ parameters: randomIntent.parameters.map((p: any) => ({
657
+ name: p.name,
658
+ extracted: Math.random() > 0.5,
659
+ value: Math.random() > 0.5 ? 'Sample Value' : null
660
+ }))
661
+ };
662
+ } else {
663
+ this.testResult = {
664
+ intent: null,
665
+ confidence: 0
666
+ };
 
 
 
 
 
667
  }
668
+
 
 
 
 
 
 
 
669
  this.testing = false;
670
  }, 1500);
671
  }
672
 
673
+ close() {
674
+ if (this.isDirty) {
675
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
676
+ width: '400px',
677
+ data: {
678
+ title: 'Unsaved Changes',
679
+ message: 'You have unsaved changes. Do you want to save before closing?',
680
+ confirmText: 'Save & Close',
681
+ cancelText: 'Discard Changes',
682
+ showThirdOption: true,
683
+ thirdOptionText: 'Cancel'
684
+ }
685
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
 
687
+ dialogRef.afterClosed().subscribe(result => {
688
+ if (result === 'confirm') {
689
+ this.saveVersion();
690
+ this.dialogRef.close();
691
+ } else if (result === 'cancel') {
692
+ this.dialogRef.close();
693
+ }
694
+ // If result is null or 'third', don't close
695
+ });
696
+ } else {
697
+ this.dialogRef.close();
698
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
  }
700
  }