import { Component, Inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatSelectModule } from '@angular/material/select'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatChipsModule } from '@angular/material/chips'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatDividerModule } from '@angular/material/divider'; import { MatListModule } from '@angular/material/list'; import { FormsModule } from '@angular/forms'; import { Version } from '../../services/api.service'; interface Difference { field: string; label: string; v1Value: any; v2Value: any; type: 'added' | 'removed' | 'modified' | 'unchanged'; } @Component({ selector: 'app-version-compare-dialog', standalone: true, imports: [ CommonModule, FormsModule, MatDialogModule, MatSelectModule, MatButtonModule, MatIconModule, MatChipsModule, MatExpansionModule, MatDividerModule, MatListModule ], template: `

Compare Versions

Version 1 Version {{ v.no }} - {{ v.caption }} (Published) compare_arrows Version 2 Version {{ v.no }} - {{ v.caption }} (Published)
add_circle {{ addedCount }} Added remove_circle {{ removedCount }} Removed edit {{ modifiedCount }} Modified
General Configuration {{ generalDifferences.length }} differences {{ getDiffIcon(diff.type) }}
{{ diff.label }}
{{ formatValue(diff.v1Value) }} arrow_forward {{ formatValue(diff.v2Value) }}
LLM Configuration {{ llmDifferences.length }} differences {{ getDiffIcon(diff.type) }}
{{ diff.label }}
{{ formatValue(diff.v1Value) }} arrow_forward {{ formatValue(diff.v2Value) }}
Intents {{ intentDifferences.added.length + intentDifferences.removed.length + intentDifferences.modified.length }} differences

add_circle Added Intents

add
{{ intent.name }}
{{ intent.caption || 'No description' }}

remove_circle Removed Intents

remove
{{ intent.name }}
{{ intent.caption || 'No description' }}

edit Modified Intents

{{ intent.name }} {{ intent.changes.length }} changes {{ getDiffIcon(change.type) }}
{{ change.label }}
{{ formatValue(change.v1Value) }} arrow_forward {{ formatValue(change.v2Value) }}
compare

Select two versions to compare

info

Please select different versions to compare

check_circle

These versions are identical

`, styles: [` .compare-container { min-width: 800px; max-width: 1000px; } .version-selectors { display: flex; gap: 24px; align-items: center; justify-content: center; margin-bottom: 32px; mat-form-field { flex: 1; max-width: 350px; } .compare-icon { font-size: 32px; width: 32px; height: 32px; color: #666; } .published-marker { color: #4caf50; font-weight: 500; margin-left: 8px; } } .summary-chips { margin-bottom: 24px; display: flex; justify-content: center; mat-chip { margin: 0 4px; mat-icon { margin-right: 4px; } } } .comparison-results { mat-expansion-panel { margin-bottom: 16px; } } .diff-values { display: flex; align-items: center; gap: 8px; margin-top: 4px; .old-value { color: #d32f2f; text-decoration: line-through; } .new-value { color: #388e3c; font-weight: 500; } mat-icon { font-size: 16px; width: 16px; height: 16px; color: #666; } } .diff-added { color: #388e3c; } .diff-removed { color: #d32f2f; } .diff-modified { color: #1976d2; } .intents-comparison { .intent-group { margin-bottom: 24px; h4 { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; color: #666; mat-icon { font-size: 20px; width: 20px; height: 20px; } } mat-expansion-panel { margin-bottom: 8px; } } } .empty-state { text-align: center; padding: 60px 20px; mat-icon { font-size: 64px; width: 64px; height: 64px; color: #e0e0e0; margin-bottom: 16px; } p { color: #666; font-size: 16px; } } `] }) export default class VersionCompareDialogComponent { versions: Version[]; version1: Version | null = null; version2: Version | null = null; differences: Difference[] = []; generalDifferences: Difference[] = []; llmDifferences: Difference[] = []; intentDifferences = { added: [] as any[], removed: [] as any[], modified: [] as any[] }; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { versions: Version[], selectedVersion?: Version } ) { this.versions = data.versions; // Pre-select versions if (data.selectedVersion) { this.version1 = data.selectedVersion; // Select the next most recent version as version2 const otherVersions = this.versions.filter(v => v.no !== data.selectedVersion!.no); if (otherVersions.length > 0) { this.version2 = otherVersions[0]; this.compareVersions(); } } } get addedCount(): number { return this.differences.filter(d => d.type === 'added').length + this.intentDifferences.added.length; } get removedCount(): number { return this.differences.filter(d => d.type === 'removed').length + this.intentDifferences.removed.length; } get modifiedCount(): number { return this.differences.filter(d => d.type === 'modified').length + this.intentDifferences.modified.length; } get hasGeneralDifferences(): boolean { return this.generalDifferences.length > 0; } get hasLLMDifferences(): boolean { return this.llmDifferences.length > 0; } get hasIntentDifferences(): boolean { return this.intentDifferences.added.length > 0 || this.intentDifferences.removed.length > 0 || this.intentDifferences.modified.length > 0; } compareVersions() { if (!this.version1 || !this.version2 || this.version1.no === this.version2.no) { this.differences = []; this.generalDifferences = []; this.llmDifferences = []; this.intentDifferences = { added: [], removed: [], modified: [] }; return; } this.differences = []; // Compare general fields this.compareField('caption', 'Caption', this.version1.caption, this.version2.caption); this.compareField('general_prompt', 'General Prompt', (this.version1 as any).general_prompt, (this.version2 as any).general_prompt); this.compareField('published', 'Published Status', this.version1.published, this.version2.published); // Compare LLM configuration if (this.version1.llm && this.version2.llm) { this.compareField('llm.repo_id', 'Model Repository', this.version1.llm.repo_id, this.version2.llm.repo_id); this.compareField('llm.use_fine_tune', 'Use Fine-Tune', this.version1.llm.use_fine_tune, this.version2.llm.use_fine_tune); if (this.version1.llm.use_fine_tune || this.version2.llm.use_fine_tune) { this.compareField('llm.fine_tune_zip', 'Fine-Tune ZIP', this.version1.llm.fine_tune_zip, this.version2.llm.fine_tune_zip); } // Compare generation config const gc1 = this.version1.llm.generation_config; const gc2 = this.version2.llm.generation_config; if (gc1 && gc2) { this.compareField('llm.generation_config.max_new_tokens', 'Max Tokens', gc1.max_new_tokens, gc2.max_new_tokens); this.compareField('llm.generation_config.temperature', 'Temperature', gc1.temperature, gc2.temperature); this.compareField('llm.generation_config.top_p', 'Top P', gc1.top_p, gc2.top_p); this.compareField('llm.generation_config.repetition_penalty', 'Repetition Penalty', gc1.repetition_penalty, gc2.repetition_penalty); } } // Compare intents this.compareIntents(); // Categorize differences this.generalDifferences = this.differences.filter(d => !d.field.startsWith('llm.') && !d.field.startsWith('intent.')); this.llmDifferences = this.differences.filter(d => d.field.startsWith('llm.')); } private compareField(field: string, label: string, v1Value: any, v2Value: any) { if (v1Value === v2Value) { return; } let type: 'added' | 'removed' | 'modified'; if (v1Value === undefined || v1Value === null || v1Value === '') { type = 'added'; } else if (v2Value === undefined || v2Value === null || v2Value === '') { type = 'removed'; } else { type = 'modified'; } this.differences.push({ field, label, v1Value, v2Value, type }); } private compareIntents() { const intents1 = this.version1?.intents || []; const intents2 = this.version2?.intents || []; const intents1Map = new Map(intents1.map(i => [i.name, i])); const intents2Map = new Map(intents2.map(i => [i.name, i])); // Find added intents this.intentDifferences.added = intents2.filter(i => !intents1Map.has(i.name)); // Find removed intents this.intentDifferences.removed = intents1.filter(i => !intents2Map.has(i.name)); // Find modified intents this.intentDifferences.modified = []; for (const [name, intent1] of intents1Map) { const intent2 = intents2Map.get(name); if (intent2) { const changes = this.compareIntentDetails(intent1, intent2); if (changes.length > 0) { this.intentDifferences.modified.push({ name, changes }); } } } } private compareIntentDetails(intent1: any, intent2: any): Difference[] { const changes: Difference[] = []; // Compare basic fields if (intent1.caption !== intent2.caption) { changes.push({ field: `intent.${intent1.name}.caption`, label: 'Caption', v1Value: intent1.caption, v2Value: intent2.caption, type: 'modified' }); } if (intent1.detection_prompt !== intent2.detection_prompt) { changes.push({ field: `intent.${intent1.name}.detection_prompt`, label: 'Detection Prompt', v1Value: intent1.detection_prompt, v2Value: intent2.detection_prompt, type: 'modified' }); } if (intent1.action !== intent2.action) { changes.push({ field: `intent.${intent1.name}.action`, label: 'API Action', v1Value: intent1.action, v2Value: intent2.action, type: 'modified' }); } // Compare examples const examples1 = intent1.examples || []; const examples2 = intent2.examples || []; if (JSON.stringify(examples1) !== JSON.stringify(examples2)) { changes.push({ field: `intent.${intent1.name}.examples`, label: 'Examples', v1Value: `${examples1.length} examples`, v2Value: `${examples2.length} examples`, type: 'modified' }); } // Compare parameters const params1 = intent1.parameters || []; const params2 = intent2.parameters || []; if (JSON.stringify(params1) !== JSON.stringify(params2)) { changes.push({ field: `intent.${intent1.name}.parameters`, label: 'Parameters', v1Value: `${params1.length} parameters`, v2Value: `${params2.length} parameters`, type: 'modified' }); } return changes; } getDiffIcon(type: string): string { switch (type) { case 'added': return 'add_circle'; case 'removed': return 'remove_circle'; case 'modified': return 'edit'; default: return 'circle'; } } formatValue(value: any): string { if (value === null || value === undefined) return 'Not set'; if (typeof value === 'boolean') return value ? 'Yes' : 'No'; if (typeof value === 'string' && value.length > 100) { return value.substring(0, 100) + '...'; } return String(value); } close() { this.dialogRef.close(); } }