Spaces:
Paused
Paused
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
|
@@ -1,83 +1,553 @@
|
|
| 1 |
-
import { Component, Inject } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import { MatButtonModule } from '@angular/material/button';
|
| 5 |
import { MatIconModule } from '@angular/material/icon';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
@Component({
|
| 8 |
selector: 'app-version-edit-dialog',
|
| 9 |
standalone: true,
|
| 10 |
-
imports: [
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
|
| 39 |
-
.
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
|
|
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
}
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
) {}
|
| 79 |
|
| 80 |
close() {
|
| 81 |
-
this.dialogRef.close(
|
| 82 |
}
|
| 83 |
}
|
|
|
|
| 1 |
+
import { Component, Inject, OnInit } from '@angular/core';
|
| 2 |
import { CommonModule } from '@angular/common';
|
| 3 |
+
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms';
|
| 4 |
+
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
| 5 |
+
import { MatTabsModule } from '@angular/material/tabs';
|
| 6 |
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
| 7 |
+
import { MatInputModule } from '@angular/material/input';
|
| 8 |
+
import { MatSelectModule } from '@angular/material/select';
|
| 9 |
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
| 10 |
import { MatButtonModule } from '@angular/material/button';
|
| 11 |
import { MatIconModule } from '@angular/material/icon';
|
| 12 |
+
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
| 13 |
+
import { MatTableModule } from '@angular/material/table';
|
| 14 |
+
import { MatChipsModule } from '@angular/material/chips';
|
| 15 |
+
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 |
@Component({
|
| 23 |
selector: 'app-version-edit-dialog',
|
| 24 |
standalone: true,
|
| 25 |
+
imports: [
|
| 26 |
+
CommonModule,
|
| 27 |
+
ReactiveFormsModule,
|
| 28 |
+
MatDialogModule,
|
| 29 |
+
MatTabsModule,
|
| 30 |
+
MatFormFieldModule,
|
| 31 |
+
MatInputModule,
|
| 32 |
+
MatSelectModule,
|
| 33 |
+
MatCheckboxModule,
|
| 34 |
+
MatButtonModule,
|
| 35 |
+
MatIconModule,
|
| 36 |
+
MatSnackBarModule,
|
| 37 |
+
MatTableModule,
|
| 38 |
+
MatChipsModule,
|
| 39 |
+
MatExpansionModule,
|
| 40 |
+
MatDividerModule,
|
| 41 |
+
MatProgressBarModule,
|
| 42 |
+
MatListModule
|
| 43 |
+
],
|
| 44 |
+
templateUrl: './version-edit-dialog.component.html',
|
| 45 |
+
styleUrls: ['./version-edit-dialog.component.scss']
|
| 46 |
+
})
|
| 47 |
+
export default class VersionEditDialogComponent implements OnInit {
|
| 48 |
+
project: Project;
|
| 49 |
+
versions: Version[] = [];
|
| 50 |
+
selectedVersion: Version | null = null;
|
| 51 |
+
versionForm!: FormGroup;
|
| 52 |
+
|
| 53 |
+
loading = false;
|
| 54 |
+
saving = false;
|
| 55 |
+
publishing = false;
|
| 56 |
+
creating = false;
|
| 57 |
+
|
| 58 |
+
selectedTabIndex = 0;
|
| 59 |
+
testUserMessage = '';
|
| 60 |
+
testResult: any = null;
|
| 61 |
+
testing = false;
|
| 62 |
+
|
| 63 |
+
constructor(
|
| 64 |
+
private fb: FormBuilder,
|
| 65 |
+
private apiService: ApiService,
|
| 66 |
+
private snackBar: MatSnackBar,
|
| 67 |
+
private dialog: MatDialog,
|
| 68 |
+
public dialogRef: MatDialogRef<VersionEditDialogComponent>,
|
| 69 |
+
@Inject(MAT_DIALOG_DATA) public data: any
|
| 70 |
+
) {
|
| 71 |
+
this.project = data.project;
|
| 72 |
+
this.versions = [...this.project.versions].sort((a, b) => b.id - a.id);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
ngOnInit() {
|
| 76 |
+
this.initializeForm();
|
| 77 |
+
|
| 78 |
+
// Select the latest unpublished version or the latest version
|
| 79 |
+
const unpublished = this.versions.find(v => !v.published);
|
| 80 |
+
this.selectedVersion = unpublished || this.versions[0] || null;
|
| 81 |
+
|
| 82 |
+
if (this.selectedVersion) {
|
| 83 |
+
this.loadVersion(this.selectedVersion);
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
initializeForm() {
|
| 88 |
+
this.versionForm = this.fb.group({
|
| 89 |
+
id: [{value: '', disabled: true}],
|
| 90 |
+
caption: ['', Validators.required],
|
| 91 |
+
published: [{value: false, disabled: true}],
|
| 92 |
+
general_prompt: ['', Validators.required],
|
| 93 |
+
llm: this.fb.group({
|
| 94 |
+
repo_id: ['', Validators.required],
|
| 95 |
+
generation_config: this.fb.group({
|
| 96 |
+
max_new_tokens: [256, [Validators.required, Validators.min(1), Validators.max(2048)]],
|
| 97 |
+
temperature: [0.2, [Validators.required, Validators.min(0), Validators.max(2)]],
|
| 98 |
+
top_p: [0.8, [Validators.required, Validators.min(0), Validators.max(1)]],
|
| 99 |
+
repetition_penalty: [1.1, [Validators.required, Validators.min(1), Validators.max(2)]]
|
| 100 |
+
}),
|
| 101 |
+
use_fine_tune: [false],
|
| 102 |
+
fine_tune_zip: ['']
|
| 103 |
+
}),
|
| 104 |
+
intents: this.fb.array([]),
|
| 105 |
+
last_update_date: ['']
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
// Watch for fine-tune toggle
|
| 109 |
+
this.versionForm.get('llm.use_fine_tune')?.valueChanges.subscribe(useFineTune => {
|
| 110 |
+
const fineTuneControl = this.versionForm.get('llm.fine_tune_zip');
|
| 111 |
+
if (useFineTune) {
|
| 112 |
+
fineTuneControl?.setValidators([Validators.required]);
|
| 113 |
+
} else {
|
| 114 |
+
fineTuneControl?.clearValidators();
|
| 115 |
+
fineTuneControl?.setValue('');
|
| 116 |
+
}
|
| 117 |
+
fineTuneControl?.updateValueAndValidity();
|
| 118 |
+
});
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
loadVersion(version: Version) {
|
| 122 |
+
this.selectedVersion = version;
|
| 123 |
|
| 124 |
+
// Clear intents array
|
| 125 |
+
const intentsArray = this.versionForm.get('intents') as FormArray;
|
| 126 |
+
while (intentsArray.length !== 0) {
|
| 127 |
+
intentsArray.removeAt(0);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// Populate form
|
| 131 |
+
this.versionForm.patchValue({
|
| 132 |
+
id: version.id,
|
| 133 |
+
caption: version.caption,
|
| 134 |
+
published: version.published,
|
| 135 |
+
general_prompt: version.general_prompt || '',
|
| 136 |
+
llm: version.llm || {
|
| 137 |
+
repo_id: '',
|
| 138 |
+
generation_config: {
|
| 139 |
+
max_new_tokens: 256,
|
| 140 |
+
temperature: 0.2,
|
| 141 |
+
top_p: 0.8,
|
| 142 |
+
repetition_penalty: 1.1
|
| 143 |
+
},
|
| 144 |
+
use_fine_tune: false,
|
| 145 |
+
fine_tune_zip: ''
|
| 146 |
+
},
|
| 147 |
+
last_update_date: version.last_update_date || ''
|
| 148 |
+
});
|
| 149 |
+
|
| 150 |
+
// Populate intents
|
| 151 |
+
if (version.intents) {
|
| 152 |
+
version.intents.forEach(intent => {
|
| 153 |
+
intentsArray.push(this.createIntentFormGroup(intent));
|
| 154 |
+
});
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
// Enable/disable form based on published status
|
| 158 |
+
if (version.published) {
|
| 159 |
+
this.versionForm.disable();
|
| 160 |
+
this.snackBar.open('This version is published and cannot be edited', 'OK', { duration: 3000 });
|
| 161 |
+
} else {
|
| 162 |
+
this.versionForm.enable();
|
| 163 |
+
this.versionForm.get('id')?.disable();
|
| 164 |
+
this.versionForm.get('published')?.disable();
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
createIntentFormGroup(intent: any = {}): FormGroup {
|
| 169 |
+
const parametersArray = this.fb.array([]);
|
| 170 |
+
|
| 171 |
+
if (intent.parameters) {
|
| 172 |
+
intent.parameters.forEach((param: any) => {
|
| 173 |
+
parametersArray.push(this.createParameterFormGroup(param));
|
| 174 |
+
});
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
return this.fb.group({
|
| 178 |
+
name: [intent.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9-]+$/)]],
|
| 179 |
+
caption: [intent.caption || ''],
|
| 180 |
+
locale: [intent.locale || 'tr-TR'],
|
| 181 |
+
detection_prompt: [intent.detection_prompt || '', Validators.required],
|
| 182 |
+
examples: this.fb.array(intent.examples || []),
|
| 183 |
+
parameters: parametersArray,
|
| 184 |
+
action: [intent.action || '', Validators.required],
|
| 185 |
+
fallback_timeout_prompt: [intent.fallback_timeout_prompt || ''],
|
| 186 |
+
fallback_error_prompt: [intent.fallback_error_prompt || '']
|
| 187 |
+
});
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
createParameterFormGroup(param: any = {}): FormGroup {
|
| 191 |
+
return this.fb.group({
|
| 192 |
+
name: [param.name || '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]],
|
| 193 |
+
caption: [param.caption || ''],
|
| 194 |
+
type: [param.type || 'str', Validators.required],
|
| 195 |
+
required: [param.required !== false],
|
| 196 |
+
variable_name: [param.variable_name || '', Validators.required],
|
| 197 |
+
extraction_prompt: [param.extraction_prompt || ''],
|
| 198 |
+
validation_regex: [param.validation_regex || ''],
|
| 199 |
+
invalid_prompt: [param.invalid_prompt || ''],
|
| 200 |
+
type_error_prompt: [param.type_error_prompt || '']
|
| 201 |
+
});
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
get intents() {
|
| 205 |
+
return this.versionForm.get('intents') as FormArray;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
getIntentParameters(intentIndex: number): FormArray {
|
| 209 |
+
return this.intents.at(intentIndex).get('parameters') as FormArray;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
getIntentExamples(intentIndex: number): FormArray {
|
| 213 |
+
return this.intents.at(intentIndex).get('examples') as FormArray;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
addIntent() {
|
| 217 |
+
this.intents.push(this.createIntentFormGroup());
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
removeIntent(index: number) {
|
| 221 |
+
const intent = this.intents.at(index).value;
|
| 222 |
+
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
| 223 |
+
width: '400px',
|
| 224 |
+
data: {
|
| 225 |
+
title: 'Delete Intent',
|
| 226 |
+
message: `Are you sure you want to delete intent "${intent.name}"?`,
|
| 227 |
+
confirmText: 'Delete',
|
| 228 |
+
confirmColor: 'warn'
|
| 229 |
+
}
|
| 230 |
+
});
|
| 231 |
+
|
| 232 |
+
dialogRef.afterClosed().subscribe(confirmed => {
|
| 233 |
+
if (confirmed) {
|
| 234 |
+
this.intents.removeAt(index);
|
| 235 |
+
}
|
| 236 |
+
});
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
async editIntent(intentIndex: number) {
|
| 240 |
+
const { default: IntentEditDialogComponent } = await import('../intent-edit-dialog/intent-edit-dialog.component');
|
| 241 |
|
| 242 |
+
const intent = this.intents.at(intentIndex);
|
| 243 |
+
const dialogRef = this.dialog.open(IntentEditDialogComponent, {
|
| 244 |
+
width: '90vw',
|
| 245 |
+
maxWidth: '1000px',
|
| 246 |
+
data: {
|
| 247 |
+
intent: intent.value,
|
| 248 |
+
project: this.project,
|
| 249 |
+
apis: await this.getAvailableAPIs()
|
| 250 |
+
}
|
| 251 |
+
});
|
| 252 |
+
|
| 253 |
+
dialogRef.afterClosed().subscribe(result => {
|
| 254 |
+
if (result) {
|
| 255 |
+
// Update the intent in the form array
|
| 256 |
+
intent.patchValue(result);
|
| 257 |
+
}
|
| 258 |
+
});
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
addParameter(intentIndex: number) {
|
| 262 |
+
const parameters = this.getIntentParameters(intentIndex);
|
| 263 |
+
parameters.push(this.createParameterFormGroup());
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
removeParameter(intentIndex: number, paramIndex: number) {
|
| 267 |
+
const parameters = this.getIntentParameters(intentIndex);
|
| 268 |
+
parameters.removeAt(paramIndex);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
addExample(intentIndex: number, example: string) {
|
| 272 |
+
if (example.trim()) {
|
| 273 |
+
const examples = this.getIntentExamples(intentIndex);
|
| 274 |
+
examples.push(this.fb.control(example));
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
removeExample(intentIndex: number, exampleIndex: number) {
|
| 279 |
+
const examples = this.getIntentExamples(intentIndex);
|
| 280 |
+
examples.removeAt(exampleIndex);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
async createVersion() {
|
| 284 |
+
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
| 285 |
+
width: '500px',
|
| 286 |
+
data: {
|
| 287 |
+
title: 'Create New Version',
|
| 288 |
+
message: 'Which version would you like to use as a base for the new version?',
|
| 289 |
+
confirmText: 'Create',
|
| 290 |
+
showVersionSelect: true,
|
| 291 |
+
versions: this.versions
|
| 292 |
+
}
|
| 293 |
+
});
|
| 294 |
+
|
| 295 |
+
dialogRef.afterClosed().subscribe(async (sourceVersionId) => {
|
| 296 |
+
if (sourceVersionId) {
|
| 297 |
+
this.creating = true;
|
| 298 |
+
try {
|
| 299 |
+
const result = await this.apiService.createVersion(this.project.id, {
|
| 300 |
+
source_version_id: sourceVersionId,
|
| 301 |
+
caption: 'New Version'
|
| 302 |
+
}).toPromise();
|
| 303 |
+
|
| 304 |
+
this.snackBar.open('Version created successfully', 'Close', { duration: 3000 });
|
| 305 |
+
|
| 306 |
+
// Reload project to get new version
|
| 307 |
+
await this.reloadProject();
|
| 308 |
+
|
| 309 |
+
// Select the new version
|
| 310 |
+
const newVersion = this.versions.find(v => v.id === result.id);
|
| 311 |
+
if (newVersion) {
|
| 312 |
+
this.loadVersion(newVersion);
|
| 313 |
+
}
|
| 314 |
+
} catch (error: any) {
|
| 315 |
+
this.snackBar.open(error.error?.detail || 'Failed to create version', 'Close', {
|
| 316 |
+
duration: 5000,
|
| 317 |
+
panelClass: 'error-snackbar'
|
| 318 |
+
});
|
| 319 |
+
} finally {
|
| 320 |
+
this.creating = false;
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
});
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
async saveVersion() {
|
| 327 |
+
if (this.versionForm.invalid || !this.selectedVersion) {
|
| 328 |
+
this.snackBar.open('Please fix all validation errors', 'Close', { duration: 3000 });
|
| 329 |
+
return;
|
| 330 |
}
|
| 331 |
|
| 332 |
+
this.saving = true;
|
| 333 |
+
try {
|
| 334 |
+
const formValue = this.versionForm.getRawValue();
|
| 335 |
+
|
| 336 |
+
// Prepare intents data
|
| 337 |
+
const intents = formValue.intents.map((intent: any) => ({
|
| 338 |
+
...intent,
|
| 339 |
+
examples: intent.examples || []
|
| 340 |
+
}));
|
| 341 |
|
| 342 |
+
const updateData = {
|
| 343 |
+
caption: formValue.caption,
|
| 344 |
+
general_prompt: formValue.general_prompt,
|
| 345 |
+
llm: formValue.llm,
|
| 346 |
+
intents: intents,
|
| 347 |
+
last_update_date: formValue.last_update_date
|
| 348 |
+
};
|
| 349 |
+
|
| 350 |
+
await this.apiService.updateVersion(
|
| 351 |
+
this.project.id,
|
| 352 |
+
this.selectedVersion.id,
|
| 353 |
+
updateData
|
| 354 |
+
).toPromise();
|
| 355 |
+
|
| 356 |
+
this.snackBar.open('Version saved successfully', 'Close', { duration: 3000 });
|
| 357 |
+
|
| 358 |
+
// Update last_update_date
|
| 359 |
+
const updatedVersion = { ...this.selectedVersion, ...updateData };
|
| 360 |
+
this.loadVersion(updatedVersion);
|
| 361 |
+
|
| 362 |
+
} catch (error: any) {
|
| 363 |
+
if (error.status === 409) {
|
| 364 |
+
this.snackBar.open('Version was modified by another user. Please reload.', 'Close', {
|
| 365 |
+
duration: 5000,
|
| 366 |
+
panelClass: 'error-snackbar'
|
| 367 |
+
});
|
| 368 |
+
await this.reloadProject();
|
| 369 |
+
} else {
|
| 370 |
+
this.snackBar.open(error.error?.detail || 'Failed to save version', 'Close', {
|
| 371 |
+
duration: 5000,
|
| 372 |
+
panelClass: 'error-snackbar'
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
} finally {
|
| 376 |
+
this.saving = false;
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
async publishVersion() {
|
| 381 |
+
if (!this.selectedVersion) return;
|
| 382 |
+
|
| 383 |
+
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
| 384 |
+
width: '500px',
|
| 385 |
+
data: {
|
| 386 |
+
title: 'Publish Version',
|
| 387 |
+
message: `Are you sure you want to publish version "${this.selectedVersion.caption}"? This will unpublish all other versions.`,
|
| 388 |
+
confirmText: 'Publish',
|
| 389 |
+
confirmColor: 'primary'
|
| 390 |
}
|
| 391 |
+
});
|
| 392 |
+
|
| 393 |
+
dialogRef.afterClosed().subscribe(async (confirmed) => {
|
| 394 |
+
if (confirmed && this.selectedVersion) {
|
| 395 |
+
this.publishing = true;
|
| 396 |
+
try {
|
| 397 |
+
await this.apiService.publishVersion(
|
| 398 |
+
this.project.id,
|
| 399 |
+
this.selectedVersion.id
|
| 400 |
+
).toPromise();
|
| 401 |
+
|
| 402 |
+
this.snackBar.open('Version published successfully', 'Close', { duration: 3000 });
|
| 403 |
+
|
| 404 |
+
// Reload to get updated data
|
| 405 |
+
await this.reloadProject();
|
| 406 |
+
|
| 407 |
+
} catch (error: any) {
|
| 408 |
+
this.snackBar.open(error.error?.detail || 'Failed to publish version', 'Close', {
|
| 409 |
+
duration: 5000,
|
| 410 |
+
panelClass: 'error-snackbar'
|
| 411 |
+
});
|
| 412 |
+
} finally {
|
| 413 |
+
this.publishing = false;
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
});
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
async deleteVersion() {
|
| 420 |
+
if (!this.selectedVersion || this.selectedVersion.published) return;
|
| 421 |
|
| 422 |
+
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
| 423 |
+
width: '400px',
|
| 424 |
+
data: {
|
| 425 |
+
title: 'Delete Version',
|
| 426 |
+
message: `Are you sure you want to delete version "${this.selectedVersion.caption}"?`,
|
| 427 |
+
confirmText: 'Delete',
|
| 428 |
+
confirmColor: 'warn'
|
| 429 |
}
|
| 430 |
+
});
|
| 431 |
|
| 432 |
+
dialogRef.afterClosed().subscribe(async (confirmed) => {
|
| 433 |
+
if (confirmed && this.selectedVersion) {
|
| 434 |
+
try {
|
| 435 |
+
await this.apiService.deleteVersion(
|
| 436 |
+
this.project.id,
|
| 437 |
+
this.selectedVersion.id
|
| 438 |
+
).toPromise();
|
| 439 |
+
|
| 440 |
+
this.snackBar.open('Version deleted successfully', 'Close', { duration: 3000 });
|
| 441 |
+
|
| 442 |
+
// Reload and select another version
|
| 443 |
+
await this.reloadProject();
|
| 444 |
+
|
| 445 |
+
if (this.versions.length > 0) {
|
| 446 |
+
this.loadVersion(this.versions[0]);
|
| 447 |
+
} else {
|
| 448 |
+
this.selectedVersion = null;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
} catch (error: any) {
|
| 452 |
+
this.snackBar.open(error.error?.detail || 'Failed to delete version', 'Close', {
|
| 453 |
+
duration: 5000,
|
| 454 |
+
panelClass: 'error-snackbar'
|
| 455 |
+
});
|
| 456 |
+
}
|
| 457 |
}
|
| 458 |
+
});
|
| 459 |
+
}
|
| 460 |
|
| 461 |
+
async testIntentDetection() {
|
| 462 |
+
if (!this.testUserMessage.trim()) {
|
| 463 |
+
this.snackBar.open('Please enter a test message', 'Close', { duration: 3000 });
|
| 464 |
+
return;
|
| 465 |
+
}
|
| 466 |
|
| 467 |
+
this.testing = true;
|
| 468 |
+
this.testResult = null;
|
| 469 |
+
|
| 470 |
+
// Simulate intent detection test
|
| 471 |
+
setTimeout(() => {
|
| 472 |
+
// This is a mock - in real implementation, this would call the Spark service
|
| 473 |
+
const intents = this.versionForm.get('intents')?.value || [];
|
| 474 |
+
|
| 475 |
+
// Simple matching for demo
|
| 476 |
+
let detectedIntent = null;
|
| 477 |
+
let confidence = 0;
|
| 478 |
+
|
| 479 |
+
for (const intent of intents) {
|
| 480 |
+
for (const example of intent.examples || []) {
|
| 481 |
+
if (this.testUserMessage.toLowerCase().includes(example.toLowerCase())) {
|
| 482 |
+
detectedIntent = intent.name;
|
| 483 |
+
confidence = 0.95;
|
| 484 |
+
break;
|
| 485 |
+
}
|
| 486 |
}
|
| 487 |
+
if (detectedIntent) break;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
// Random detection for demo
|
| 491 |
+
if (!detectedIntent && intents.length > 0) {
|
| 492 |
+
const randomIntent = intents[Math.floor(Math.random() * intents.length)];
|
| 493 |
+
detectedIntent = randomIntent.name;
|
| 494 |
+
confidence = 0.65;
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
this.testResult = {
|
| 498 |
+
success: true,
|
| 499 |
+
intent: detectedIntent,
|
| 500 |
+
confidence: confidence,
|
| 501 |
+
parameters: detectedIntent ? this.extractTestParameters(detectedIntent) : []
|
| 502 |
+
};
|
| 503 |
+
|
| 504 |
+
this.testing = false;
|
| 505 |
+
}, 1500);
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
private extractTestParameters(intentName: string): any[] {
|
| 509 |
+
// Mock parameter extraction
|
| 510 |
+
const intent = this.intents.value.find((i: any) => i.name === intentName);
|
| 511 |
+
if (!intent) return [];
|
| 512 |
+
|
| 513 |
+
return intent.parameters.map((param: any) => ({
|
| 514 |
+
name: param.name,
|
| 515 |
+
value: param.type === 'date' ? '2025-06-15' : 'test_value',
|
| 516 |
+
extracted: Math.random() > 0.3
|
| 517 |
+
}));
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
async getAvailableAPIs(): Promise<any[]> {
|
| 521 |
+
try {
|
| 522 |
+
return await this.apiService.getAPIs().toPromise() || [];
|
| 523 |
+
} catch {
|
| 524 |
+
return [];
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
private async reloadProject() {
|
| 529 |
+
this.loading = true;
|
| 530 |
+
try {
|
| 531 |
+
const projects = await this.apiService.getProjects().toPromise() || [];
|
| 532 |
+
const updatedProject = projects.find(p => p.id === this.project.id);
|
| 533 |
+
|
| 534 |
+
if (updatedProject) {
|
| 535 |
+
this.project = updatedProject;
|
| 536 |
+
this.versions = [...updatedProject.versions].sort((a, b) => b.id - a.id);
|
| 537 |
}
|
| 538 |
+
} catch (error) {
|
| 539 |
+
console.error('Failed to reload project:', error);
|
| 540 |
+
} finally {
|
| 541 |
+
this.loading = false;
|
| 542 |
}
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
compareVersions() {
|
| 546 |
+
// TODO: Implement version comparison
|
| 547 |
+
this.snackBar.open('Version comparison coming soon', 'Close', { duration: 3000 });
|
| 548 |
+
}
|
|
|
|
| 549 |
|
| 550 |
close() {
|
| 551 |
+
this.dialogRef.close(true);
|
| 552 |
}
|
| 553 |
}
|