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

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
@@ -17,6 +17,7 @@ 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';
@@ -59,7 +60,8 @@ interface Locale {
59
  MatDividerModule,
60
  MatProgressBarModule,
61
  MatListModule,
62
- MatProgressSpinnerModule
 
63
  ],
64
  templateUrl: './version-edit-dialog.component.html',
65
  styleUrls: ['./version-edit-dialog.component.scss']
@@ -69,13 +71,17 @@ export default class VersionEditDialogComponent implements OnInit {
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';
@@ -83,46 +89,66 @@ export default class VersionEditDialogComponent implements OnInit {
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
 
@@ -130,17 +156,34 @@ export default class VersionEditDialogComponent implements OnInit {
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
  }
@@ -154,101 +197,105 @@ export default class VersionEditDialogComponent implements OnInit {
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
 
@@ -415,8 +462,12 @@ export default class VersionEditDialogComponent implements OnInit {
415
  });
416
  }
417
 
418
- async getAvailableAPIs() {
419
- return this.api.getAPIs().toPromise();
 
 
 
 
420
  }
421
 
422
  async createVersion() {
@@ -440,236 +491,418 @@ export default class VersionEditDialogComponent implements OnInit {
440
 
441
  dialogRef.afterClosed().subscribe(async (result) => {
442
  if (result?.confirmed) {
443
- this.loading = true;
444
  try {
445
  let newVersionData;
446
 
447
  if (result.selectedValue) {
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;
490
  }
491
  }
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, {
 
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 { MatBadgeModule } from '@angular/material/badge';
21
  import { ApiService, Project, Version } from '../../services/api.service';
22
  import { LocaleManagerService } from '../../services/locale-manager.service';
23
  import ConfirmDialogComponent from '../confirm-dialog/confirm-dialog.component';
 
60
  MatDividerModule,
61
  MatProgressBarModule,
62
  MatListModule,
63
+ MatProgressSpinnerModule,
64
+ MatBadgeModule
65
  ],
66
  templateUrl: './version-edit-dialog.component.html',
67
  styleUrls: ['./version-edit-dialog.component.scss']
 
71
  versions: Version[] = [];
72
  selectedVersion: Version | null = null;
73
  versionForm!: FormGroup;
74
+
75
  loading = false;
76
  saving = false;
77
  publishing = false;
78
+ creating = false;
79
+ isDirty = false;
80
  testing = false;
81
+
82
+ selectedTabIndex = 0;
83
+ testUserMessage = '';
84
  testResult: any = null;
 
85
 
86
  // Multi-language support
87
  selectedExampleLocale: string = 'tr';
 
89
 
90
  constructor(
91
  private fb: FormBuilder,
92
+ private apiService: ApiService,
93
  private localeService: LocaleManagerService,
94
  private snackBar: MatSnackBar,
95
  private dialog: MatDialog,
96
  public dialogRef: MatDialogRef<VersionEditDialogComponent>,
97
+ @Inject(MAT_DIALOG_DATA) public data: any
98
  ) {
99
  this.project = data.project;
100
+ this.versions = [...this.project.versions].sort((a, b) => b.no - a.no);
101
  this.selectedExampleLocale = this.project.default_locale || 'tr';
102
  }
103
 
104
  ngOnInit() {
105
  this.initializeForm();
 
106
  this.loadAvailableLocales();
107
+
108
+ // Select the latest unpublished version or the latest version
109
+ const unpublished = this.versions.find(v => !v.published);
110
+ this.selectedVersion = unpublished || this.versions[0] || null;
111
+
112
+ if (this.selectedVersion) {
113
+ this.loadVersion(this.selectedVersion);
114
+ }
115
+
116
+ this.versionForm.valueChanges.subscribe(() => {
117
+ this.isDirty = true;
118
+ });
119
  }
120
 
121
  initializeForm() {
122
  this.versionForm = this.fb.group({
123
+ no: [{value: '', disabled: true}],
124
+ caption: ['', Validators.required],
125
+ published: [{value: false, disabled: true}],
126
+ general_prompt: ['', Validators.required],
127
+ llm: this.fb.group({
128
+ repo_id: ['', Validators.required],
129
+ generation_config: this.fb.group({
130
+ max_new_tokens: [256, [Validators.required, Validators.min(1), Validators.max(2048)]],
131
+ temperature: [0.2, [Validators.required, Validators.min(0), Validators.max(2)]],
132
+ top_p: [0.8, [Validators.required, Validators.min(0), Validators.max(1)]],
133
+ repetition_penalty: [1.1, [Validators.required, Validators.min(1), Validators.max(2)]]
134
+ }),
135
+ use_fine_tune: [false],
136
+ fine_tune_zip: ['']
137
  }),
138
+ intents: this.fb.array([]),
139
+ last_update_date: ['']
 
 
 
140
  });
141
 
142
+ // Watch for fine-tune toggle
143
+ this.versionForm.get('llm.use_fine_tune')?.valueChanges.subscribe(useFineTune => {
144
+ const fineTuneControl = this.versionForm.get('llm.fine_tune_zip');
145
+ if (useFineTune) {
146
+ fineTuneControl?.setValidators([Validators.required]);
147
+ } else {
148
+ fineTuneControl?.clearValidators();
149
+ fineTuneControl?.setValue('');
150
+ }
151
+ fineTuneControl?.updateValueAndValidity();
152
  });
153
  }
154
 
 
156
  // Get supported locales from project
157
  const supportedCodes = [
158
  this.project.default_locale,
159
+ ...(this.project.supported_locales || [])
160
  ].filter(Boolean);
161
 
162
  // Get locale details
163
  for (const code of supportedCodes) {
164
+ try {
165
+ const localeInfo = await this.localeService.getLocaleDetails(code).toPromise();
166
+ if (localeInfo) {
167
+ this.availableLocales.push({
168
+ code: localeInfo.code,
169
+ name: localeInfo.name
170
+ });
171
+ }
172
+ } catch (error) {
173
+ // Use fallback for known locales
174
+ const fallbackNames: { [key: string]: string } = {
175
+ 'tr': 'Türkçe',
176
+ 'en': 'English',
177
+ 'de': 'Deutsch',
178
+ 'fr': 'Français',
179
+ 'es': 'Español'
180
+ };
181
+ if (fallbackNames[code]) {
182
+ this.availableLocales.push({
183
+ code: code,
184
+ name: fallbackNames[code]
185
+ });
186
+ }
187
  }
188
  }
189
  }
 
197
  return locale?.name || localeCode;
198
  }
199
 
200
+ loadVersion(version: Version) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  this.selectedVersion = version;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ // Form değerlerini set et
204
  this.versionForm.patchValue({
205
+ no: version.no,
206
  caption: version.caption || '',
207
+ published: version.published || false,
208
+ general_prompt: (version as any).general_prompt || '',
209
+ last_update_date: version.last_update_date || ''
 
 
210
  });
211
+
212
+ // LLM config'i ayrı set et
213
+ if ((version as any).llm) {
214
+ this.versionForm.patchValue({
215
+ llm: {
216
+ repo_id: (version as any).llm.repo_id || '',
217
+ generation_config: (version as any).llm.generation_config || {
218
+ max_new_tokens: 512,
219
+ temperature: 0.7,
220
+ top_p: 0.95,
221
+ repetition_penalty: 1.1
222
+ },
223
+ use_fine_tune: (version as any).llm.use_fine_tune || false,
224
+ fine_tune_zip: (version as any).llm.fine_tune_zip || ''
225
+ }
226
  });
227
  }
228
+
229
+ // Clear and rebuild intents
230
+ this.intents.clear();
231
+ ((version as any).intents || []).forEach((intent: any) => {
232
+ this.intents.push(this.createIntentFormGroup(intent));
233
+ });
234
+
235
  this.isDirty = false;
236
  }
237
+
238
+ async loadVersions() {
239
+ this.loading = true;
240
+ try {
241
+ const project = await this.apiService.getProject(this.project.id).toPromise();
242
+ if (project) {
243
+ this.project = project;
244
+ this.versions = [...project.versions].sort((a, b) => b.no - a.no);
245
+
246
+ // Re-select current version if it still exists
247
+ if (this.selectedVersion) {
248
+ const currentVersion = this.versions.find(v => v.no === this.selectedVersion!.no);
249
+ if (currentVersion) {
250
+ this.loadVersion(currentVersion);
251
+ } else if (this.versions.length > 0) {
252
+ this.loadVersion(this.versions[0]);
253
+ }
254
+ } else if (this.versions.length > 0) {
255
+ this.loadVersion(this.versions[0]);
256
+ }
257
+ }
258
+ } catch (error) {
259
+ this.snackBar.open('Failed to reload versions', 'Close', { duration: 3000 });
260
+ } finally {
261
+ this.loading = false;
262
+ }
263
+ }
264
 
265
+ createIntentFormGroup(intent: any = {}): FormGroup {
266
+ const group = this.fb.group({
267
+ name: [intent.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9-]+$/)]],
268
+ caption: [intent.caption || ''],
269
+ detection_prompt: [intent.detection_prompt || '', Validators.required],
270
+ examples: [intent.examples || []], // Store as array, not FormArray
271
+ parameters: this.fb.array([]),
272
+ action: [intent.action || '', Validators.required],
273
+ fallback_timeout_prompt: [intent.fallback_timeout_prompt || ''],
274
+ fallback_error_prompt: [intent.fallback_error_prompt || '']
 
 
 
 
275
  });
276
+
277
+ // Parameters'ı ayrı olarak ekle
278
+ if (intent.parameters && Array.isArray(intent.parameters)) {
279
+ const parametersArray = group.get('parameters') as FormArray;
280
+ intent.parameters.forEach((param: any) => {
281
+ parametersArray.push(this.createParameterFormGroup(param));
282
+ });
283
+ }
284
+
285
+ return group;
286
  }
287
 
288
+ createParameterFormGroup(param: any = {}): FormGroup {
289
  return this.fb.group({
290
+ name: [param.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]],
291
+ caption: [param.caption || []],
292
+ type: [param.type || 'str', Validators.required],
293
+ required: [param.required !== false],
294
+ variable_name: [param.variable_name || '', Validators.required],
295
+ extraction_prompt: [param.extraction_prompt || ''],
296
+ validation_regex: [param.validation_regex || ''],
297
+ invalid_prompt: [param.invalid_prompt || ''],
298
+ type_error_prompt: [param.type_error_prompt || '']
299
  });
300
  }
301
 
 
462
  });
463
  }
464
 
465
+ async getAvailableAPIs(): Promise<any[]> {
466
+ try {
467
+ return await this.apiService.getAPIs().toPromise() || [];
468
+ } catch {
469
+ return [];
470
+ }
471
  }
472
 
473
  async createVersion() {
 
491
 
492
  dialogRef.afterClosed().subscribe(async (result) => {
493
  if (result?.confirmed) {
494
+ this.creating = true;
495
  try {
496
  let newVersionData;
497
 
498
  if (result.selectedValue) {
499
+ // Copy from selected version - we need to get the full version data
500
  const sourceVersion = this.versions.find(v => v.no === result.selectedValue);
501
  if (sourceVersion) {
502
+ // Load the full version data from the current form if it's the selected version
503
+ if (sourceVersion.no === this.selectedVersion?.no) {
504
+ const formValue = this.versionForm.getRawValue();
505
+ newVersionData = {
506
+ ...formValue,
507
+ no: undefined,
508
+ published: false,
509
+ last_update_date: undefined,
510
+ caption: `Copy of ${sourceVersion.caption || `version ${sourceVersion.no}`}`
511
+ };
512
+ } else {
513
+ // For other versions, we only have basic info, so create minimal copy
514
+ newVersionData = {
515
+ caption: `Copy of ${sourceVersion.caption || `version ${sourceVersion.no}`}`,
516
+ general_prompt: '',
517
+ llm: {
518
+ repo_id: '',
519
+ generation_config: {
520
+ max_new_tokens: 512,
521
+ temperature: 0.7,
522
+ top_p: 0.95,
523
+ repetition_penalty: 1.1
524
+ },
525
+ use_fine_tune: false,
526
+ fine_tune_zip: ''
527
+ },
528
+ intents: []
529
+ };
530
+ }
531
  }
532
  } else {
533
  // Create blank version
534
  newVersionData = {
535
+ caption: `Version ${this.versions.length + 1}`,
536
+ general_prompt: '',
537
+ llm: {
538
+ repo_id: '',
539
+ generation_config: {
540
+ max_new_tokens: 512,
541
+ temperature: 0.7,
542
+ top_p: 0.95,
543
+ repetition_penalty: 1.1
544
+ },
545
+ use_fine_tune: false,
546
+ fine_tune_zip: ''
547
  },
548
  intents: []
549
  };
550
  }
551
+
552
+ if (newVersionData) {
553
+ await this.apiService.createVersion(this.project.id, newVersionData).toPromise();
554
+ await this.loadVersions();
555
+ this.snackBar.open('Version created successfully!', 'Close', { duration: 3000 });
556
+ }
 
 
 
557
  } catch (error) {
 
558
  this.snackBar.open('Failed to create version', 'Close', { duration: 3000 });
559
  } finally {
560
+ this.creating = false;
561
  }
562
  }
563
  });
564
  }
565
 
566
+ async saveVersion() {
567
+ if (!this.selectedVersion || !this.canEdit) {
568
+ this.snackBar.open('Cannot save published version', 'Close', { duration: 3000 });
569
+ return;
570
+ }
571
+
572
+ if (this.versionForm.invalid) {
573
+ const invalidFields: string[] = [];
574
+ Object.keys(this.versionForm.controls).forEach(key => {
575
+ const control = this.versionForm.get(key);
576
+ if (control && control.invalid) {
577
+ invalidFields.push(key);
578
+ }
579
+ });
580
+
581
+ this.intents.controls.forEach((intent, index) => {
582
+ if (intent.invalid) {
583
+ invalidFields.push(`Intent ${index + 1}`);
584
+ }
585
+ });
586
+
587
+ this.snackBar.open(`Please fix validation errors in: ${invalidFields.join(', ')}`, 'Close', {
588
+ duration: 5000
589
+ });
590
+ return;
591
+ }
592
+
593
+ const currentVersion = this.selectedVersion!;
594
+
595
+ this.saving = true;
596
+
597
+ try {
598
+ const formValue = this.versionForm.getRawValue();
599
+
600
+ // updateData'yı backend'in beklediği formatta hazırla
601
+ const updateData = {
602
+ caption: formValue.caption,
603
+ general_prompt: formValue.general_prompt || '',
604
+ llm: formValue.llm,
605
+ intents: formValue.intents.map((intent: any) => ({
606
+ name: intent.name,
607
+ caption: intent.caption,
608
+ detection_prompt: intent.detection_prompt,
609
+ examples: Array.isArray(intent.examples) ? intent.examples : [],
610
+ parameters: Array.isArray(intent.parameters) ? intent.parameters.map((param: any) => ({
611
+ name: param.name,
612
+ caption: param.caption,
613
+ type: param.type,
614
+ required: param.required,
615
+ variable_name: param.variable_name,
616
+ extraction_prompt: param.extraction_prompt,
617
+ validation_regex: param.validation_regex,
618
+ invalid_prompt: param.invalid_prompt,
619
+ type_error_prompt: param.type_error_prompt
620
+ })) : [],
621
+ action: intent.action,
622
+ fallback_timeout_prompt: intent.fallback_timeout_prompt,
623
+ fallback_error_prompt: intent.fallback_error_prompt
624
+ })),
625
+ last_update_date: currentVersion.last_update_date || ''
626
+ };
627
+
628
+ console.log('Saving version data:', JSON.stringify(updateData, null, 2));
629
+
630
+ const result = await this.apiService.updateVersion(
631
+ this.project.id,
632
+ currentVersion.no,
633
+ updateData
634
+ ).toPromise();
635
+
636
+ this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 });
637
+
638
+ this.isDirty = false;
639
+
640
+ if (result) {
641
+ this.selectedVersion = result;
642
+ this.versionForm.patchValue({
643
+ last_update_date: result.last_update_date
644
+ });
645
+ }
646
+
647
+ await this.loadVersions();
648
+
649
+ } catch (error: any) {
650
+ console.error('Save error:', error);
651
+
652
+ if (error.status === 409) {
653
+ // Race condition handling
654
+ await this.handleRaceCondition(currentVersion);
655
+ } else if (error.status === 400 && error.error?.detail?.includes('Published versions')) {
656
+ this.snackBar.open('Published versions cannot be modified. Create a new version instead.', 'Close', {
657
+ duration: 5000,
658
+ panelClass: 'error-snackbar'
659
+ });
660
+ } else {
661
+ const errorMessage = error.error?.detail || error.message || 'Failed to save version';
662
+ this.snackBar.open(errorMessage, 'Close', {
663
+ duration: 5000,
664
+ panelClass: 'error-snackbar'
665
+ });
666
+ }
667
+ } finally {
668
+ this.saving = false;
669
+ }
670
+ }
671
+
672
+ // Race condition handling
673
+ private async handleRaceCondition(currentVersion: Version) {
674
+ const formValue = this.versionForm.getRawValue();
675
 
676
+ const retryUpdateData = {
677
+ caption: formValue.caption,
678
+ general_prompt: formValue.general_prompt || '',
679
+ llm: formValue.llm,
680
+ intents: formValue.intents.map((intent: any) => ({
681
+ name: intent.name,
682
+ caption: intent.caption,
683
+ detection_prompt: intent.detection_prompt,
684
+ examples: Array.isArray(intent.examples) ? intent.examples : [],
685
+ parameters: Array.isArray(intent.parameters) ? intent.parameters : [],
686
+ action: intent.action,
687
+ fallback_timeout_prompt: intent.fallback_timeout_prompt,
688
+ fallback_error_prompt: intent.fallback_error_prompt
689
+ })),
690
+ last_update_date: currentVersion.last_update_date || ''
691
+ };
692
+
693
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
694
+ width: '500px',
695
  data: {
696
+ title: 'Version Modified',
697
+ message: 'This version was modified by another user. Do you want to reload and lose your changes, or force save?',
698
+ confirmText: 'Force Save',
699
+ cancelText: 'Reload',
700
+ confirmColor: 'warn'
701
  }
702
  });
703
+
704
+ dialogRef.afterClosed().subscribe(async (forceSave) => {
705
+ if (forceSave) {
 
706
  try {
707
+ await this.apiService.updateVersion(
708
+ this.project.id,
709
+ currentVersion.no,
710
+ retryUpdateData,
711
+ true
712
+ ).toPromise();
713
+ this.snackBar.open('Version force saved', 'Close', { duration: 3000 });
 
 
 
 
714
  await this.loadVersions();
715
+ } catch (err: any) {
716
+ this.snackBar.open(err.error?.detail || 'Force save failed', 'Close', {
717
+ duration: 5000,
718
+ panelClass: 'error-snackbar'
719
+ });
 
 
 
720
  }
721
+ } else {
722
+ await this.loadVersions();
723
  }
724
  });
725
+ }
726
+
727
+ async publishVersion() {
728
+ if (!this.selectedVersion) return;
 
 
 
 
 
 
 
 
 
 
729
 
 
 
 
730
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
731
+ width: '500px',
732
  data: {
733
+ title: 'Publish Version',
734
+ message: `Are you sure you want to publish version "${this.selectedVersion.caption}"? This will unpublish all other versions.`,
735
+ confirmText: 'Publish',
736
+ confirmColor: 'primary'
737
  }
738
  });
739
 
740
  dialogRef.afterClosed().subscribe(async (confirmed) => {
741
+ if (confirmed && this.selectedVersion) {
742
+ this.publishing = true;
743
  try {
744
+ await this.apiService.publishVersion(
745
+ this.project.id,
746
+ this.selectedVersion.no
747
+ ).toPromise();
748
+
749
+ this.snackBar.open('Version published successfully', 'Close', { duration: 3000 });
750
 
751
+ // Reload to get updated data
752
+ await this.reloadProject();
753
 
754
+ } catch (error: any) {
755
+ this.snackBar.open(error.error?.detail || 'Failed to publish version', 'Close', {
756
+ duration: 5000,
757
+ panelClass: 'error-snackbar'
758
+ });
759
  } finally {
760
+ this.publishing = false;
761
  }
762
  }
763
  });
764
  }
765
 
766
+ async deleteVersion() {
767
+ if (!this.selectedVersion || this.selectedVersion.published) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
768
 
 
 
 
769
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {
770
+ width: '400px',
771
  data: {
772
+ title: 'Delete Version',
773
+ message: `Are you sure you want to delete version "${this.selectedVersion.caption}"?`,
774
+ confirmText: 'Delete',
775
+ confirmColor: 'warn'
776
  }
777
  });
778
 
779
+ dialogRef.afterClosed().subscribe(async (confirmed) => {
780
+ if (confirmed && this.selectedVersion) {
781
+ try {
782
+ await this.apiService.deleteVersion(
783
+ this.project.id,
784
+ this.selectedVersion.no
785
+ ).toPromise();
786
+
787
+ this.snackBar.open('Version deleted successfully', 'Close', { duration: 3000 });
788
+
789
+ // Reload and select another version
790
+ await this.reloadProject();
791
+
792
+ if (this.versions.length > 0) {
793
+ this.loadVersion(this.versions[0]);
794
+ } else {
795
+ this.selectedVersion = null;
796
  }
797
+
798
+ } catch (error: any) {
799
+ this.snackBar.open(error.error?.detail || 'Failed to delete version', 'Close', {
800
+ duration: 5000,
801
+ panelClass: 'error-snackbar'
802
+ });
803
+ }
804
  }
805
  });
806
  }
807
 
808
+ async testIntentDetection() {
809
+ if (!this.testUserMessage.trim()) {
810
+ this.snackBar.open('Please enter a test message', 'Close', { duration: 3000 });
811
+ return;
812
+ }
813
+
814
  this.testing = true;
815
  this.testResult = null;
816
+
817
  // Simulate intent detection test
818
  setTimeout(() => {
819
+ // This is a mock - in real implementation, this would call the Spark service
820
+ const intents = this.versionForm.get('intents')?.value || [];
821
 
822
+ // Simple matching for demo
823
+ let detectedIntent = null;
824
+ let confidence = 0;
825
+
826
+ for (const intent of intents) {
827
+ // Check examples in all locales
828
+ const allExamples = intent.examples || [];
829
+ for (const example of allExamples) {
830
+ const exampleText = typeof example === 'string' ? example : example.example;
831
+ if (this.testUserMessage.toLowerCase().includes(exampleText.toLowerCase())) {
832
+ detectedIntent = intent.name;
833
+ confidence = 0.95;
834
+ break;
835
+ }
836
+ }
837
+ if (detectedIntent) break;
 
 
838
  }
839
+
840
+ // Random detection for demo
841
+ if (!detectedIntent && intents.length > 0) {
842
+ const randomIntent = intents[Math.floor(Math.random() * intents.length)];
843
+ detectedIntent = randomIntent.name;
844
+ confidence = 0.65;
845
+ }
846
+
847
+ this.testResult = {
848
+ success: true,
849
+ intent: detectedIntent,
850
+ confidence: confidence,
851
+ parameters: detectedIntent ? this.extractTestParameters(detectedIntent) : []
852
+ };
853
+
854
  this.testing = false;
855
  }, 1500);
856
  }
857
 
858
+ private extractTestParameters(intentName: string): any[] {
859
+ // Mock parameter extraction
860
+ const intent = this.intents.value.find((i: any) => i.name === intentName);
861
+ if (!intent) return [];
862
+
863
+ return intent.parameters.map((param: any) => ({
864
+ name: param.name,
865
+ value: param.type === 'date' ? '2025-06-15' : 'test_value',
866
+ extracted: Math.random() > 0.3
867
+ }));
868
+ }
869
+
870
+ private async reloadProject() {
871
+ this.loading = true;
872
+ try {
873
+ const projects = await this.apiService.getProjects().toPromise() || [];
874
+ const updatedProject = projects.find(p => p.id === this.project.id);
875
+
876
+ if (updatedProject) {
877
+ this.project = updatedProject;
878
+ this.versions = [...updatedProject.versions].sort((a, b) => b.no - a.no);
879
+ }
880
+ } catch (error) {
881
+ console.error('Failed to reload project:', error);
882
+ } finally {
883
+ this.loading = false;
884
+ }
885
+ }
886
+
887
+ async compareVersions() {
888
+ if (this.versions.length < 2) {
889
+ this.snackBar.open('Need at least 2 versions to compare', 'Close', { duration: 3000 });
890
+ return;
891
+ }
892
+
893
+ const { default: VersionCompareDialogComponent } = await import('../version-compare-dialog/version-compare-dialog.component');
894
+
895
+ this.dialog.open(VersionCompareDialogComponent, {
896
+ width: '90vw',
897
+ maxWidth: '1000px',
898
+ maxHeight: '80vh',
899
+ data: {
900
+ versions: this.versions,
901
+ selectedVersion: this.selectedVersion
902
+ }
903
+ });
904
+ }
905
+
906
  close() {
907
  if (this.isDirty) {
908
  const dialogRef = this.dialog.open(ConfirmDialogComponent, {