Spaces:
Running
Running
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 |
-
|
|
|
|
|
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
|
87 |
private localeService: LocaleManagerService,
|
88 |
private snackBar: MatSnackBar,
|
89 |
private dialog: MatDialog,
|
90 |
public dialogRef: MatDialogRef<VersionEditDialogComponent>,
|
91 |
-
@Inject(MAT_DIALOG_DATA) public data:
|
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 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
|
|
115 |
}),
|
116 |
-
|
117 |
-
|
118 |
-
adapter_path: ['']
|
119 |
-
}),
|
120 |
-
intents: this.fb.array([])
|
121 |
});
|
122 |
|
123 |
-
//
|
124 |
-
this.versionForm.valueChanges.subscribe(
|
125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
134 |
].filter(Boolean);
|
135 |
|
136 |
// Get locale details
|
137 |
for (const code of supportedCodes) {
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
}
|
145 |
}
|
146 |
}
|
@@ -154,101 +197,105 @@ export default class VersionEditDialogComponent implements OnInit {
|
|
154 |
return locale?.name || localeCode;
|
155 |
}
|
156 |
|
157 |
-
|
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 |
-
//
|
205 |
this.versionForm.patchValue({
|
|
|
206 |
caption: version.caption || '',
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
generation_config: version.generation_config || {},
|
211 |
-
fine_tune: version.fine_tune || { enabled: false }
|
212 |
});
|
213 |
-
|
214 |
-
//
|
215 |
-
if (version
|
216 |
-
|
217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
});
|
219 |
}
|
220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
this.isDirty = false;
|
222 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
-
createIntentFormGroup(intent
|
225 |
-
|
226 |
-
name: [intent
|
227 |
-
caption: [intent
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
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
|
242 |
return this.fb.group({
|
243 |
-
name: [param
|
244 |
-
caption: [param
|
245 |
-
type: [param
|
246 |
-
required: [param
|
247 |
-
variable_name: [param
|
248 |
-
extraction_prompt: [param
|
249 |
-
validation_regex: [param
|
250 |
-
invalid_prompt: [param
|
251 |
-
type_error_prompt: [param
|
252 |
});
|
253 |
}
|
254 |
|
@@ -415,8 +462,12 @@ export default class VersionEditDialogComponent implements OnInit {
|
|
415 |
});
|
416 |
}
|
417 |
|
418 |
-
async getAvailableAPIs() {
|
419 |
-
|
|
|
|
|
|
|
|
|
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.
|
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 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
}
|
460 |
} else {
|
461 |
// Create blank version
|
462 |
newVersionData = {
|
463 |
-
caption:
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
|
|
|
|
|
|
472 |
},
|
473 |
intents: []
|
474 |
};
|
475 |
}
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
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.
|
490 |
}
|
491 |
}
|
492 |
});
|
493 |
}
|
494 |
|
495 |
-
async
|
496 |
-
if (!this.selectedVersion)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
499 |
-
width: '
|
500 |
data: {
|
501 |
-
title: '
|
502 |
-
message:
|
503 |
-
confirmText: '
|
504 |
-
cancelText: '
|
|
|
505 |
}
|
506 |
});
|
507 |
-
|
508 |
-
dialogRef.afterClosed().subscribe(async (
|
509 |
-
if (
|
510 |
-
this.loading = true;
|
511 |
try {
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
};
|
520 |
-
|
521 |
-
const newVersion = await this.api.createVersion(this.project.id, copyData).toPromise();
|
522 |
-
|
523 |
await this.loadVersions();
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
this.snackBar.open('Failed to copy version', 'Close', { duration: 3000 });
|
530 |
-
} finally {
|
531 |
-
this.loading = false;
|
532 |
}
|
|
|
|
|
533 |
}
|
534 |
});
|
535 |
-
}
|
536 |
-
|
537 |
-
async
|
538 |
-
|
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: '
|
555 |
data: {
|
556 |
-
title: '
|
557 |
-
message: `Are you sure you want to
|
558 |
-
confirmText: '
|
559 |
-
confirmColor: '
|
560 |
}
|
561 |
});
|
562 |
|
563 |
dialogRef.afterClosed().subscribe(async (confirmed) => {
|
564 |
-
if (confirmed) {
|
565 |
-
this.
|
566 |
try {
|
567 |
-
await this.
|
|
|
|
|
|
|
|
|
|
|
568 |
|
569 |
-
|
570 |
-
await this.
|
571 |
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
|
|
576 |
} finally {
|
577 |
-
this.
|
578 |
}
|
579 |
}
|
580 |
});
|
581 |
}
|
582 |
|
583 |
-
|
584 |
-
if (!this.selectedVersion ||
|
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: '
|
608 |
data: {
|
609 |
-
title: '
|
610 |
-
message: `
|
611 |
-
confirmText: '
|
612 |
-
confirmColor: '
|
613 |
}
|
614 |
});
|
615 |
|
616 |
-
dialogRef.afterClosed().subscribe((confirmed) => {
|
617 |
-
if (confirmed) {
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
this.
|
633 |
}
|
634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
635 |
}
|
636 |
});
|
637 |
}
|
638 |
|
639 |
-
testIntentDetection() {
|
640 |
-
if (!this.
|
641 |
-
|
|
|
|
|
|
|
642 |
this.testing = true;
|
643 |
this.testResult = null;
|
644 |
-
|
645 |
// Simulate intent detection test
|
646 |
setTimeout(() => {
|
647 |
-
|
|
|
648 |
|
649 |
-
// Simple
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
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, {
|