ciyidogan commited on
Commit
42e8706
·
verified ·
1 Parent(s): d8f52d8

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 { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
 
 
 
 
 
 
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: [CommonModule, MatDialogModule, MatButtonModule, MatIconModule],
11
- template: `
12
- <h2 mat-dialog-title>Manage Versions - {{ data.project.name }}</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- <mat-dialog-content>
15
- <div class="version-management-placeholder">
16
- <mat-icon>construction</mat-icon>
17
- <h3>Version Management Coming Soon</h3>
18
- <p>This feature will allow you to:</p>
19
- <ul>
20
- <li>Create new versions from existing ones</li>
21
- <li>Edit unpublished versions</li>
22
- <li>Publish versions to production</li>
23
- <li>Compare versions side by side</li>
24
- <li>Manage intents and parameters</li>
25
- </ul>
26
- </div>
27
- </mat-dialog-content>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- <mat-dialog-actions align="end">
30
- <button mat-button (click)="close()">Close</button>
31
- </mat-dialog-actions>
32
- `,
33
- styles: [`
34
- mat-dialog-content {
35
- min-width: 600px;
36
- min-height: 400px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
- .version-management-placeholder {
40
- text-align: center;
41
- padding: 60px 20px;
 
 
 
 
 
 
42
 
43
- mat-icon {
44
- font-size: 64px;
45
- width: 64px;
46
- height: 64px;
47
- color: #666;
48
- margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- h3 {
52
- color: #333;
53
- margin-bottom: 16px;
 
 
 
 
54
  }
 
55
 
56
- p {
57
- color: #666;
58
- margin-bottom: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
 
 
60
 
61
- ul {
62
- text-align: left;
63
- max-width: 400px;
64
- margin: 0 auto;
65
- color: #666;
66
 
67
- li {
68
- margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
 
 
 
 
71
  }
72
- `]
73
- })
74
- export default class VersionEditDialogComponent {
75
- constructor(
76
- public dialogRef: MatDialogRef<VersionEditDialogComponent>,
77
- @Inject(MAT_DIALOG_DATA) public data: any
78
- ) {}
79
 
80
  close() {
81
- this.dialogRef.close(false);
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
  }