UNPKG

answer-perfect-form-component

Version:

A simple standalone Angular form question component that makes API calls on blur with AI-powered text cleanup - Updated for Angular 18.2.x compatibility

539 lines (535 loc) 74 kB
import { Component, Input, inject, ElementRef, ViewChild, } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import * as i0 from "@angular/core"; import * as i1 from "@angular/common"; import * as i2 from "@angular/forms"; /** * Rating enum matching the backend */ export var Rating; (function (Rating) { Rating["NoRating"] = "No rating"; Rating["Weak"] = "Weak"; Rating["Sufficient"] = "Sufficient"; Rating["Strong"] = "Strong"; })(Rating || (Rating = {})); /** * A reusable Angular component that provides a textarea input for answering questions. * Automatically submits answers to an AI evaluation API when the user stops typing. * Displays AI feedback in a tooltip on hover over the rating bar. * Streams audio of the feedback text when hovering over the rating bar (configurable). */ export class FormQuestionComponent { constructor() { this.http = inject(HttpClient); /** Whether to enable audio feedback on hover (default: true) */ this.enableAudio = true; this.answerText = ''; this.isSubmitting = false; this.errorMessage = ''; this.feedbackText = ''; this.rating = null; this.isTyping = false; this.isCleaning = false; this.debounceTimeout = null; this.cancelRequest$ = new Subject(); this.currentAudioUrl = null; this.currentAudioRequest$ = new Subject(); } ngOnInit() { // Validate required inputs if (!this.apiKey || !this.endpoint || !this.questionId) { console.error('FormQuestionComponent: Missing required inputs'); } console.log('FormQuestionComponent initialized'); console.log('enableAudio:', this.enableAudio); } ngAfterViewInit() { console.log('View initialized, hoverAudio element:', this.hoverAudio); if (this.hoverAudio && this.hoverAudio.nativeElement) { console.log('Audio element is available and ready'); } else { console.warn('Audio element not found or not ready'); } } ngOnDestroy() { this.clearDebounceTimeout(); this.cancelRequest$.next(); this.cancelRequest$.complete(); this.currentAudioRequest$.next(); this.currentAudioRequest$.complete(); // Clean up any existing audio blob URL if (this.currentAudioUrl) { URL.revokeObjectURL(this.currentAudioUrl); this.currentAudioUrl = null; } } /** * Play streaming audio when mouse enters the rating bar */ onRatingBarHover() { console.log('Rating bar hover detected'); console.log('enableAudio:', this.enableAudio); console.log('feedbackText:', this.feedbackText); console.log('feedbackText.trim():', this.feedbackText?.trim()); console.log('hoverAudio:', this.hoverAudio); console.log('hoverAudio.nativeElement:', this.hoverAudio?.nativeElement); // Wait for audio element to be available if it's not ready yet if (!this.hoverAudio || !this.hoverAudio.nativeElement) { console.log('Audio element not ready, waiting...'); setTimeout(() => { this.onRatingBarHover(); }, 100); return; } if (this.enableAudio && this.feedbackText && this.feedbackText.trim() && this.hoverAudio && this.hoverAudio.nativeElement) { console.log('All conditions met, calling playFeedbackAudio'); this.playFeedbackAudio(this.feedbackText); } else { console.log('Conditions not met for audio playback'); if (!this.enableAudio) console.log('Audio disabled'); if (!this.feedbackText) console.log('No feedback text'); if (!this.feedbackText?.trim()) console.log('Feedback text is empty'); if (!this.hoverAudio) console.log('No hover audio element'); if (!this.hoverAudio?.nativeElement) console.log('No hover audio native element'); } } /** * Stop audio when mouse leaves the rating bar */ onRatingBarLeave() { console.log('Rating bar leave detected'); if (this.enableAudio && this.hoverAudio && this.hoverAudio.nativeElement) { // Cancel any ongoing audio request this.currentAudioRequest$.next(); // Pause and reset audio this.hoverAudio.nativeElement.pause(); this.hoverAudio.nativeElement.currentTime = 0; // Clean up the current audio blob URL if (this.currentAudioUrl) { URL.revokeObjectURL(this.currentAudioUrl); this.currentAudioUrl = null; } } else { console.log('Cannot stop audio - conditions not met'); } } /** * Play streaming audio for the given feedback text */ playFeedbackAudio(feedbackText) { console.log('playFeedbackAudio called with:', feedbackText); console.log('endpoint:', this.endpoint); console.log('apiKey:', this.apiKey ? 'Present' : 'Missing'); if (!this.hoverAudio || !this.hoverAudio.nativeElement) { console.log('Audio element not available'); return; } const headers = new HttpHeaders({ 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }); const payload = { textToSpeak: feedbackText, }; console.log('Making API call to:', `${this.endpoint}/audio/stream`); console.log('Payload:', payload); // Create a blob URL from the streaming audio response this.http .post(`${this.endpoint}/audio/stream`, payload, { headers, responseType: 'blob', }) .pipe(takeUntil(this.currentAudioRequest$)) .subscribe({ next: (blob) => { console.log('Audio stream received, blob size:', blob.size); // Clean up previous audio URL if it exists if (this.currentAudioUrl) { URL.revokeObjectURL(this.currentAudioUrl); } const audioUrl = URL.createObjectURL(blob); this.currentAudioUrl = audioUrl; this.hoverAudio.nativeElement.src = audioUrl; this.hoverAudio.nativeElement.play().catch((error) => { console.warn('Could not play feedback audio:', error); }); }, error: (error) => { console.warn('Could not stream feedback audio:', error); }, }); } onBlur() { // Just clear typing state when focus is lost, no immediate submission this.isTyping = false; } onInput() { // Clear any existing timer and cancel ongoing API requests this.clearDebounceTimeout(); this.cancelRequest$.next(); // Set typing state this.isTyping = true; // Clear feedback when user deletes all text if (!this.answerText.trim()) { this.feedbackText = ''; this.rating = null; this.isTyping = false; return; } // Single timer: 2 seconds after user stops typing this.debounceTimeout = setTimeout(() => { this.isTyping = false; if (this.answerText.trim() && !this.isSubmitting) { // Clear old rating before submitting for new one this.feedbackText = ''; this.rating = null; this.submitAnswer(); } }, 2000); } clearDebounceTimeout() { if (this.debounceTimeout) { clearTimeout(this.debounceTimeout); this.debounceTimeout = null; } } submitAnswer() { // Clear timer when submitting this.clearDebounceTimeout(); this.isSubmitting = true; this.errorMessage = ''; const headers = new HttpHeaders({ 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }); const payload = { questionId: this.questionId, answerText: this.answerText.trim(), }; this.http .post(`${this.endpoint}/submitAnswer`, payload, { headers }) .pipe(takeUntil(this.cancelRequest$)) .subscribe({ next: (response) => { this.isSubmitting = false; // Display feedback and rating from AI if (response.feedbackText) { this.feedbackText = response.feedbackText; console.log('Feedback text received:', this.feedbackText); } if (response.rating) { this.rating = response.rating; console.log('Rating received:', this.rating); } }, error: (error) => { this.isSubmitting = false; // Clear any existing rating/feedback and show error in rating bar this.feedbackText = ''; this.rating = null; this.errorMessage = 'Connection error'; // Clear error message after 3 seconds and clear text to show branding setTimeout(() => { this.errorMessage = ''; this.answerText = ''; // Clear text to revert to AnswerPerfect AI branding }, 3000); }, }); } cleanupText() { if (!this.answerText.trim() || this.isCleaning) { return; } this.isCleaning = true; const headers = new HttpHeaders({ 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }); const payload = { text: this.answerText.trim(), }; this.http .post(`${this.endpoint}/cleanupText`, payload, { headers }) .pipe(takeUntil(this.cancelRequest$)) .subscribe({ next: (response) => { this.isCleaning = false; if (response.success && response.cleanedText) { this.answerText = response.cleanedText; // The text change will automatically trigger onInput() via ngModel } }, error: (error) => { this.isCleaning = false; console.error('Text cleanup failed:', error); }, }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormQuestionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormQuestionComponent, isStandalone: true, selector: "app-form-question", inputs: { apiKey: "apiKey", endpoint: "endpoint", questionId: "questionId", enableAudio: "enableAudio" }, viewQueries: [{ propertyName: "hoverAudio", first: true, predicate: ["hoverAudio"], descendants: true }], ngImport: i0, template: ` <div class="form-question-container"> <!-- Hidden audio element for streaming audio --> <audio #hoverAudio preload="none"></audio> <div class="form-field"> <div class="textarea-container"> <textarea id="answer-textarea" [(ngModel)]="answerText" (blur)="onBlur()" (input)="onInput()" rows="3" placeholder="Type your answer here..." class="answer-textarea" > </textarea> <!-- Rating bar - always visible (shows branding when empty, rating when has content) --> <div class="rating-container"> <!-- Edit icon for text cleanup --> <button *ngIf="!isCleaning" class="edit-icon" [class.disabled]="!answerText.trim()" (click)="cleanupText()" [title]=" answerText.trim() ? 'Fix spelling and grammar with AI' : 'Add text to enable AI editing' " > ✏️ </button> <div *ngIf="isCleaning" class="cleaning-spinner" title="AI is fixing your text..." > ✨ </div> <div class="feedback-bar" (mouseenter)="onRatingBarHover()" (mouseleave)="onRatingBarLeave()" [ngClass]="{ outline: !rating || rating === 'No rating' || !answerText.trim(), weak: rating === 'Weak' && answerText.trim() && !isTyping && !isSubmitting, sufficient: rating === 'Sufficient' && answerText.trim() && !isTyping && !isSubmitting, strong: rating === 'Strong' && answerText.trim() && !isTyping && !isSubmitting, noRating: rating === 'No rating' && answerText.trim() && !isTyping && !isSubmitting, branding: !answerText.trim(), typing: (isTyping || isSubmitting) && answerText.trim(), error: errorMessage && answerText.trim() }" > <span class="rating-label" *ngIf=" answerText.trim() && rating && !isTyping && !isSubmitting && !errorMessage " >{{ rating }}</span > <span class="typing-label" *ngIf=" answerText.trim() && (isTyping || isSubmitting) && !errorMessage " ><span class="dot1">.</span><span class="dot2">.</span ><span class="dot3">.</span></span > <span class="error-label" *ngIf="answerText.trim() && errorMessage" >Error</span > <span class="branding-label" *ngIf="!answerText.trim()" >AnswerPerfect AI <!-- Branding tooltip --> <div class="branding-tooltip"> AI-powered feedback to help you write better answers </div> </span> <!-- Tooltip feedback text on hover (only show if feedback exists) --> <div class="feedback-tooltip" *ngIf="feedbackText && answerText.trim()" > {{ feedbackText }} </div> </div> </div> </div> </div> </div> `, isInline: true, styles: [".form-question-container{margin-bottom:1rem}.form-field{display:flex;flex-direction:column;margin-bottom:.5rem}.textarea-container{position:relative;display:block}.answer-textarea{width:100%;padding:.75rem;border:1px solid #ccc;border-radius:4px;font-family:inherit;font-size:1rem;resize:vertical;min-height:80px}.answer-textarea:focus{outline:none;border-color:#1976d2;box-shadow:0 0 0 2px #1976d233}.rating-container{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:8px;position:relative}.edit-icon{background:none;border:none;cursor:pointer;font-size:16px;padding:4px;border-radius:4px;transition:background-color .2s ease;display:flex;align-items:center;justify-content:center;min-width:24px;height:24px}.edit-icon:hover{background-color:#0000001a}.edit-icon.disabled{opacity:.3;cursor:not-allowed;pointer-events:none}.edit-icon.disabled:hover{background-color:transparent}.cleaning-spinner{font-size:16px;animation:spin 1s linear infinite;display:flex;align-items:center;justify-content:center;min-width:24px;height:24px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.feedback-bar{height:20px;width:120px;background:#e0e0e0;border-radius:9px;position:relative;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease}.feedback-bar:hover{transform:scale(1.05)}.feedback-bar:before{content:\"\";position:absolute;left:0;top:0;height:100%;transition:width .6s ease-in-out;border-radius:9px}.feedback-bar.weak:before{background:linear-gradient(to right,#ff6b6b,#ff8e8e);width:33%}.feedback-bar.sufficient:before{background:linear-gradient(to right,#ffd93d,#ffe066);width:66%}.feedback-bar.strong:before{background:linear-gradient(to right,#6bcf7f,#8ed99e);width:100%}.feedback-bar.outline:before{background:transparent;width:0%}.feedback-bar.noRating:before{background:linear-gradient(to right,#9e9e9e,#bdbdbd);width:0%}.feedback-bar.error:before{background:linear-gradient(to right,#f44336,#ef5350);width:100%}.rating-label{font-weight:600;font-size:11px;text-align:center;color:#333;text-shadow:0 1px 2px rgba(255,255,255,.8);z-index:1;position:relative}.branding-label{font-weight:600;font-size:10px;text-align:center;color:#666;z-index:1;position:relative;letter-spacing:.5px;cursor:pointer}.branding-tooltip{position:absolute;bottom:100%;right:0;background:#333;color:#fff;padding:12px 16px;border-radius:8px;font-size:12px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;z-index:1000;margin-bottom:8px;width:280px;white-space:normal;line-height:1.4;box-shadow:0 4px 12px #0003;font-weight:500;letter-spacing:normal}.branding-tooltip:after{content:\"\";position:absolute;top:100%;right:20px;border:5px solid transparent;border-top-color:#333}.branding-label:hover .branding-tooltip{opacity:1;visibility:visible}.feedback-tooltip{position:absolute;bottom:100%;right:0;transform:translate(0);background:#333;color:#fff;padding:18px 24px;border-radius:10px;font-size:14px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;z-index:1000;margin-bottom:12px;width:450px;white-space:normal;line-height:1.6;box-shadow:0 6px 20px #0003}.feedback-tooltip:after{content:\"\";position:absolute;top:100%;right:20px;border:6px solid transparent;border-top-color:#333}.typing-label{font-weight:700;font-size:14px;text-align:center;color:#222;z-index:1;position:relative;display:flex;justify-content:center;gap:2px}.typing-label .dot1,.typing-label .dot2,.typing-label .dot3{animation:typing-pulse 1.2s infinite}.typing-label .dot1{animation-delay:0s}.typing-label .dot2{animation-delay:.15s}.typing-label .dot3{animation-delay:.3s}.feedback-bar.typing:before{background:#e0e0e0;width:0%}@keyframes typing-pulse{0%,70%,to{opacity:.2;transform:scale(.8)}35%{opacity:1;transform:scale(1.4)}}.error-label{font-weight:600;font-size:11px;text-align:center;color:#333;text-shadow:0 1px 2px rgba(255,255,255,.8);z-index:1;position:relative}.feedback-bar:hover .feedback-tooltip{opacity:1;visibility:visible}.error-message{color:#f44336;font-size:.875rem;margin-top:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormQuestionComponent, decorators: [{ type: Component, args: [{ selector: 'app-form-question', template: ` <div class="form-question-container"> <!-- Hidden audio element for streaming audio --> <audio #hoverAudio preload="none"></audio> <div class="form-field"> <div class="textarea-container"> <textarea id="answer-textarea" [(ngModel)]="answerText" (blur)="onBlur()" (input)="onInput()" rows="3" placeholder="Type your answer here..." class="answer-textarea" > </textarea> <!-- Rating bar - always visible (shows branding when empty, rating when has content) --> <div class="rating-container"> <!-- Edit icon for text cleanup --> <button *ngIf="!isCleaning" class="edit-icon" [class.disabled]="!answerText.trim()" (click)="cleanupText()" [title]=" answerText.trim() ? 'Fix spelling and grammar with AI' : 'Add text to enable AI editing' " > ✏️ </button> <div *ngIf="isCleaning" class="cleaning-spinner" title="AI is fixing your text..." > ✨ </div> <div class="feedback-bar" (mouseenter)="onRatingBarHover()" (mouseleave)="onRatingBarLeave()" [ngClass]="{ outline: !rating || rating === 'No rating' || !answerText.trim(), weak: rating === 'Weak' && answerText.trim() && !isTyping && !isSubmitting, sufficient: rating === 'Sufficient' && answerText.trim() && !isTyping && !isSubmitting, strong: rating === 'Strong' && answerText.trim() && !isTyping && !isSubmitting, noRating: rating === 'No rating' && answerText.trim() && !isTyping && !isSubmitting, branding: !answerText.trim(), typing: (isTyping || isSubmitting) && answerText.trim(), error: errorMessage && answerText.trim() }" > <span class="rating-label" *ngIf=" answerText.trim() && rating && !isTyping && !isSubmitting && !errorMessage " >{{ rating }}</span > <span class="typing-label" *ngIf=" answerText.trim() && (isTyping || isSubmitting) && !errorMessage " ><span class="dot1">.</span><span class="dot2">.</span ><span class="dot3">.</span></span > <span class="error-label" *ngIf="answerText.trim() && errorMessage" >Error</span > <span class="branding-label" *ngIf="!answerText.trim()" >AnswerPerfect AI <!-- Branding tooltip --> <div class="branding-tooltip"> AI-powered feedback to help you write better answers </div> </span> <!-- Tooltip feedback text on hover (only show if feedback exists) --> <div class="feedback-tooltip" *ngIf="feedbackText && answerText.trim()" > {{ feedbackText }} </div> </div> </div> </div> </div> </div> `, standalone: true, imports: [CommonModule, FormsModule], styles: [".form-question-container{margin-bottom:1rem}.form-field{display:flex;flex-direction:column;margin-bottom:.5rem}.textarea-container{position:relative;display:block}.answer-textarea{width:100%;padding:.75rem;border:1px solid #ccc;border-radius:4px;font-family:inherit;font-size:1rem;resize:vertical;min-height:80px}.answer-textarea:focus{outline:none;border-color:#1976d2;box-shadow:0 0 0 2px #1976d233}.rating-container{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:8px;position:relative}.edit-icon{background:none;border:none;cursor:pointer;font-size:16px;padding:4px;border-radius:4px;transition:background-color .2s ease;display:flex;align-items:center;justify-content:center;min-width:24px;height:24px}.edit-icon:hover{background-color:#0000001a}.edit-icon.disabled{opacity:.3;cursor:not-allowed;pointer-events:none}.edit-icon.disabled:hover{background-color:transparent}.cleaning-spinner{font-size:16px;animation:spin 1s linear infinite;display:flex;align-items:center;justify-content:center;min-width:24px;height:24px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.feedback-bar{height:20px;width:120px;background:#e0e0e0;border-radius:9px;position:relative;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease}.feedback-bar:hover{transform:scale(1.05)}.feedback-bar:before{content:\"\";position:absolute;left:0;top:0;height:100%;transition:width .6s ease-in-out;border-radius:9px}.feedback-bar.weak:before{background:linear-gradient(to right,#ff6b6b,#ff8e8e);width:33%}.feedback-bar.sufficient:before{background:linear-gradient(to right,#ffd93d,#ffe066);width:66%}.feedback-bar.strong:before{background:linear-gradient(to right,#6bcf7f,#8ed99e);width:100%}.feedback-bar.outline:before{background:transparent;width:0%}.feedback-bar.noRating:before{background:linear-gradient(to right,#9e9e9e,#bdbdbd);width:0%}.feedback-bar.error:before{background:linear-gradient(to right,#f44336,#ef5350);width:100%}.rating-label{font-weight:600;font-size:11px;text-align:center;color:#333;text-shadow:0 1px 2px rgba(255,255,255,.8);z-index:1;position:relative}.branding-label{font-weight:600;font-size:10px;text-align:center;color:#666;z-index:1;position:relative;letter-spacing:.5px;cursor:pointer}.branding-tooltip{position:absolute;bottom:100%;right:0;background:#333;color:#fff;padding:12px 16px;border-radius:8px;font-size:12px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;z-index:1000;margin-bottom:8px;width:280px;white-space:normal;line-height:1.4;box-shadow:0 4px 12px #0003;font-weight:500;letter-spacing:normal}.branding-tooltip:after{content:\"\";position:absolute;top:100%;right:20px;border:5px solid transparent;border-top-color:#333}.branding-label:hover .branding-tooltip{opacity:1;visibility:visible}.feedback-tooltip{position:absolute;bottom:100%;right:0;transform:translate(0);background:#333;color:#fff;padding:18px 24px;border-radius:10px;font-size:14px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;z-index:1000;margin-bottom:12px;width:450px;white-space:normal;line-height:1.6;box-shadow:0 6px 20px #0003}.feedback-tooltip:after{content:\"\";position:absolute;top:100%;right:20px;border:6px solid transparent;border-top-color:#333}.typing-label{font-weight:700;font-size:14px;text-align:center;color:#222;z-index:1;position:relative;display:flex;justify-content:center;gap:2px}.typing-label .dot1,.typing-label .dot2,.typing-label .dot3{animation:typing-pulse 1.2s infinite}.typing-label .dot1{animation-delay:0s}.typing-label .dot2{animation-delay:.15s}.typing-label .dot3{animation-delay:.3s}.feedback-bar.typing:before{background:#e0e0e0;width:0%}@keyframes typing-pulse{0%,70%,to{opacity:.2;transform:scale(.8)}35%{opacity:1;transform:scale(1.4)}}.error-label{font-weight:600;font-size:11px;text-align:center;color:#333;text-shadow:0 1px 2px rgba(255,255,255,.8);z-index:1;position:relative}.feedback-bar:hover .feedback-tooltip{opacity:1;visibility:visible}.error-message{color:#f44336;font-size:.875rem;margin-top:.25rem}\n"] }] }], propDecorators: { apiKey: [{ type: Input }], endpoint: [{ type: Input }], questionId: [{ type: Input }], enableAudio: [{ type: Input }], hoverAudio: [{ type: ViewChild, args: ['hoverAudio', { static: false }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS1xdWVzdGlvbi5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZm9ybS1xdWVzdGlvbi5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFDVCxLQUFLLEVBRUwsTUFBTSxFQUVOLFVBQVUsRUFDVixTQUFTLEdBRVYsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMvRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDL0IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGdCQUFnQixDQUFDOzs7O0FBRTNDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksTUFLWDtBQUxELFdBQVksTUFBTTtJQUNoQixnQ0FBc0IsQ0FBQTtJQUN0Qix1QkFBYSxDQUFBO0lBQ2IsbUNBQXlCLENBQUE7SUFDekIsMkJBQWlCLENBQUE7QUFDbkIsQ0FBQyxFQUxXLE1BQU0sS0FBTixNQUFNLFFBS2pCO0FBMGNEOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLHFCQUFxQjtJQWxibEM7UUFtYm1CLFNBQUksR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7UUFXM0MsZ0VBQWdFO1FBQ3ZELGdCQUFXLEdBQVksSUFBSSxDQUFDO1FBTXJDLGVBQVUsR0FBVyxFQUFFLENBQUM7UUFDeEIsaUJBQVksR0FBWSxLQUFLLENBQUM7UUFDOUIsaUJBQVksR0FBVyxFQUFFLENBQUM7UUFDMUIsaUJBQVksR0FBVyxFQUFFLENBQUM7UUFDMUIsV0FBTSxHQUFrQixJQUFJLENBQUM7UUFDN0IsYUFBUSxHQUFZLEtBQUssQ0FBQztRQUMxQixlQUFVLEdBQVksS0FBSyxDQUFDO1FBRXBCLG9CQUFlLEdBQVEsSUFBSSxDQUFDO1FBQzVCLG1CQUFjLEdBQUcsSUFBSSxPQUFPLEVBQVEsQ0FBQztRQUNyQyxvQkFBZSxHQUFrQixJQUFJLENBQUM7UUFDdEMseUJBQW9CLEdBQUcsSUFBSSxPQUFPLEVBQVEsQ0FBQztLQXdScEQ7SUF0UkMsUUFBUTtRQUNOLDJCQUEyQjtRQUMzQixJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDdkQsT0FBTyxDQUFDLEtBQUssQ0FBQyxnREFBZ0QsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLG1DQUFtQyxDQUFDLENBQUM7UUFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ2hELENBQUM7SUFFRCxlQUFlO1FBQ2IsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1Q0FBdUMsRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDdEUsSUFBSSxJQUFJLENBQUMsVUFBVSxJQUFJLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxFQUFFLENBQUM7WUFDckQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDO1FBQ3RELENBQUM7YUFBTSxDQUFDO1lBQ04sT0FBTyxDQUFDLElBQUksQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDO1FBQ3ZELENBQUM7SUFDSCxDQUFDO0lBRUQsV0FBVztRQUNULElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1FBQzVCLElBQUksQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0IsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUMvQixJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDakMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRXJDLHVDQUF1QztRQUN2QyxJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUN6QixHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztZQUMxQyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztRQUM5QixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsZ0JBQWdCO1FBQ2QsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO1FBQ3pDLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUM5QyxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDaEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsRUFBRSxJQUFJLENBQUMsWUFBWSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDL0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQzVDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLEVBQUUsSUFBSSxDQUFDLFVBQVUsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUV6RSwrREFBK0Q7UUFDL0QsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQ3ZELE9BQU8sQ0FBQyxHQUFHLENBQUMscUNBQXFDLENBQUMsQ0FBQztZQUNuRCxVQUFVLENBQUMsR0FBRyxFQUFFO2dCQUNkLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQzFCLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUNSLE9BQU87UUFDVCxDQUFDO1FBRUQsSUFDRSxJQUFJLENBQUMsV0FBVztZQUNoQixJQUFJLENBQUMsWUFBWTtZQUNqQixJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRTtZQUN4QixJQUFJLENBQUMsVUFBVTtZQUNmLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxFQUM3QixDQUFDO1lBQ0QsT0FBTyxDQUFDLEdBQUcsQ0FBQywrQ0FBK0MsQ0FBQyxDQUFDO1lBQzdELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDNUMsQ0FBQzthQUFNLENBQUM7WUFDTixPQUFPLENBQUMsR0FBRyxDQUFDLHVDQUF1QyxDQUFDLENBQUM7WUFDckQsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXO2dCQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUNyRCxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVk7Z0JBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO1lBQ3hELElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLElBQUksRUFBRTtnQkFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLHdCQUF3QixDQUFDLENBQUM7WUFDdEUsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVO2dCQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsd0JBQXdCLENBQUMsQ0FBQztZQUM1RCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxhQUFhO2dCQUNqQyxPQUFPLENBQUMsR0FBRyxDQUFDLCtCQUErQixDQUFDLENBQUM7UUFDakQsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILGdCQUFnQjtRQUNkLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUN6QyxJQUFJLElBQUksQ0FBQyxXQUFXLElBQUksSUFBSSxDQUFDLFVBQVUsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQ3pFLG1DQUFtQztZQUNuQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxFQUFFLENBQUM7WUFFakMsd0JBQXdCO1lBQ3hCLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ3RDLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUM7WUFFOUMsc0NBQXNDO1lBQ3RDLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN6QixHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztnQkFDMUMsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7WUFDOUIsQ0FBQztRQUNILENBQUM7YUFBTSxDQUFDO1lBQ04sT0FBTyxDQUFDLEdBQUcsQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO1FBQ3hELENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxpQkFBaUIsQ0FBQyxZQUFvQjtRQUM1QyxPQUFPLENBQUMsR0FBRyxDQUFDLGdDQUFnQyxFQUFFLFlBQVksQ0FBQyxDQUFDO1FBQzVELE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUN4QyxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBRTVELElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUN2RCxPQUFPLENBQUMsR0FBRyxDQUFDLDZCQUE2QixDQUFDLENBQUM7WUFDM0MsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLFdBQVcsQ0FBQztZQUM5QixjQUFjLEVBQUUsa0JBQWtCO1lBQ2xDLGFBQWEsRUFBRSxVQUFVLElBQUksQ0FBQyxNQUFNLEVBQUU7U0FDdkMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxPQUFPLEdBQUc7WUFDZCxXQUFXLEVBQUUsWUFBWTtTQUMxQixDQUFDO1FBRUYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSxHQUFHLElBQUksQ0FBQyxRQUFRLGVBQWUsQ0FBQyxDQUFDO1FBQ3BFLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBRWpDLHNEQUFzRDtRQUN0RCxJQUFJLENBQUMsSUFBSTthQUNOLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLGVBQWUsRUFBRSxPQUFPLEVBQUU7WUFDOUMsT0FBTztZQUNQLFlBQVksRUFBRSxNQUFNO1NBQ3JCLENBQUM7YUFDRCxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO2FBQzFDLFNBQVMsQ0FBQztZQUNULElBQUksRUFBRSxDQUFDLElBQVUsRUFBRSxFQUFFO2dCQUNuQixPQUFPLENBQUMsR0FBRyxDQUFDLG1DQUFtQyxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDNUQsMkNBQTJDO2dCQUMzQyxJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztvQkFDekIsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7Z0JBQzVDLENBQUM7Z0JBRUQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDM0MsSUFBSSxDQUFDLGVBQWUsR0FBRyxRQUFRLENBQUM7Z0JBQ2hDLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLEdBQUcsR0FBRyxRQUFRLENBQUM7Z0JBQzdDLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO29CQUNuRCxPQUFPLENBQUMsSUFBSSxDQUFDLGdDQUFnQyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUN4RCxDQUFDLENBQUMsQ0FBQztZQUNMLENBQUM7WUFDRCxLQUFLLEVBQUUsQ0FBQyxLQUFVLEVBQUUsRUFBRTtnQkFDcEIsT0FBTyxDQUFDLElBQUksQ0FBQyxrQ0FBa0MsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUMxRCxDQUFDO1NBQ0YsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVELE1BQU07UUFDSixzRUFBc0U7UUFDdEUsSUFBSSxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFDeEIsQ0FBQztJQUVELE9BQU87UUFDTCwyREFBMkQ7UUFDM0QsSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7UUFDNUIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUUzQixtQkFBbUI7UUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7UUFFckIsNENBQTRDO1FBQzVDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7WUFDNUIsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLENBQUM7WUFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7WUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUM7WUFDdEIsT0FBTztRQUNULENBQUM7UUFFRCxrREFBa0Q7UUFDbEQsSUFBSSxDQUFDLGVBQWUsR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFO1lBQ3JDLElBQUksQ0FBQyxRQUFRLEdBQUcsS0FBSyxDQUFDO1lBRXRCLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztnQkFDakQsaURBQWlEO2dCQUNqRCxJQUFJLENBQUMsWUFBWSxHQUFHLEVBQUUsQ0FBQztnQkFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7Z0JBQ25CLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUN0QixDQUFDO1FBQ0gsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ1gsQ0FBQztJQUVPLG9CQUFvQjtRQUMxQixJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUN6QixZQUFZLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQ25DLElBQUksQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDO1FBQzlCLENBQUM7SUFDSCxDQUFDO0lBRU8sWUFBWTtRQUNsQiw4QkFBOEI7UUFDOUIsSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7UUFFNUIsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUM7UUFDekIsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLENBQUM7UUFFdkIsTUFBTSxPQUFPLEdBQUcsSUFBSSxXQUFXLENBQUM7WUFDOUIsY0FBYyxFQUFFLGtCQUFrQjtZQUNsQyxhQUFhLEVBQUUsVUFBVSxJQUFJLENBQUMsTUFBTSxFQUFFO1NBQ3ZDLENBQUMsQ0FBQztRQUVILE1BQU0sT0FBTyxHQUFHO1lBQ2QsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO1lBQzNCLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRTtTQUNuQyxDQUFDO1FBRUYsSUFBSSxDQUFDLElBQUk7YUFDTixJQUFJLENBQWMsR0FBRyxJQUFJLENBQUMsUUFBUSxlQUFlLEVBQUUsT0FBTyxFQUFFLEVBQUUsT0FBTyxFQUFFLENBQUM7YUFDeEUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7YUFDcEMsU0FBUyxDQUFDO1lBQ1QsSUFBSSxFQUFFLENBQUMsUUFBcUIsRUFBRSxFQUFFO2dCQUM5QixJQUFJLENBQUMsWUFBWSxHQUFHLEtBQUssQ0FBQztnQkFFMUIsc0NBQXNDO2dCQUN0QyxJQUFJLFFBQVEsQ0FBQyxZQUFZLEVBQUUsQ0FBQztvQkFDMUIsSUFBSSxDQUFDLFlBQVksR0FBRyxRQUFRLENBQUMsWUFBWSxDQUFDO29CQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLHlCQUF5QixFQUFFLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztnQkFDNUQsQ0FBQztnQkFDRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztvQkFDcEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDO29CQUM5QixPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDL0MsQ0FBQztZQUNILENBQUM7WUFDRCxLQUFLLEVBQUUsQ0FBQyxLQUFVLEVBQUUsRUFBRTtnQkFDcEIsSUFBSSxDQUFDLFlBQVksR0FBRyxLQUFLLENBQUM7Z0JBRTFCLGtFQUFrRTtnQkFDbEUsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLENBQUM7Z0JBQ3ZCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO2dCQUNuQixJQUFJLENBQUMsWUFBWSxHQUFHLGtCQUFrQixDQUFDO2dCQUV2QyxzRUFBc0U7Z0JBQ3RFLFVBQVUsQ0FBQyxHQUFHLEVBQUU7b0JBQ2QsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLENBQUM7b0JBQ3ZCLElBQUksQ0FBQyxVQUFVLEdBQUcsRUFBRSxDQUFDLENBQUMsb0RBQW9EO2dCQUM1RSxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDWCxDQUFDO1NBQ0YsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVELFdBQVc7UUFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsSUFBSSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDL0MsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztRQUV2QixNQUFNLE9BQU8sR0FBRyxJQUFJLFdBQVcsQ0FBQztZQUM5QixjQUFjLEVBQUUsa0JBQWtCO1lBQ2xDLGFBQWEsRUFBRSxVQUFVLElBQUksQ0FBQyxNQUFNLEVBQUU7U0FDdkMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxPQUFPLEdBQUc7WUFDZCxJQUFJLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUU7U0FDN0IsQ0FBQztRQUVGLElBQUksQ0FBQyxJQUFJO2FBQ04sSUFBSSxDQUNILEdBQUcsSUFBSSxDQUFDLFFBQVEsY0FBYyxFQUM5QixPQUFPLEVBQ1AsRUFBRSxPQUFPLEVBQUUsQ0FDWjthQUNBLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO2FBQ3BDLFNBQVMsQ0FBQztZQUNULElBQUksRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFO2dCQUNqQixJQUFJLENBQUMsVUFBVSxHQUFHLEtBQUssQ0FBQztnQkFDeEIsSUFBSSxRQUFRLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztvQkFDN0MsSUFBSSxDQUFDLFVBQVUsR0FBRyxRQUFRLENBQUMsV0FBVyxDQUFDO29CQUN2QyxtRUFBbUU7Z0JBQ3JFLENBQUM7WUFDSCxDQUFDO1lBQ0QsS0FBSyxFQUFFLENBQUMsS0FBVSxFQUFFLEVBQUU7Z0JBQ3BCLElBQUksQ0FBQyxVQUFVLEdBQUcsS0FBSyxDQUFDO2dCQUN4QixPQUFPLENBQUMsS0FBSyxDQUFDLHNCQUFzQixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQy9DLENBQUM7U0FDRixDQUFDLENBQUM7SUFDUCxDQUFDOytHQXJUVSxxQkFBcUI7bUdBQXJCLHFCQUFxQixpU0FoYnRCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0hULCtrSUFrVFMsWUFBWSxnT0FBRSxXQUFXOzs0RkFReEIscUJBQXFCO2tCQWxiakMsU0FBUzsrQkFDRSxtQkFBbUIsWUFDbkI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzSFQsY0FpVFcsSUFBSSxXQUNQLENBQUMsWUFBWSxFQUFFLFdBQVcsQ0FBQzs4QkFZM0IsTUFBTTtzQkFBZCxLQUFLO2dCQUdHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBR0csVUFBVTtzQkFBbEIsS0FBSztnQkFHRyxXQUFXO3NCQUFuQixLQUFLO2dCQUlOLFVBQVU7c0JBRFQsU0FBUzt1QkFBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcclxuICBDb21wb25lbnQsXHJcbiAgSW5wdXQsXHJcbiAgT25Jbml0LFxyXG4gIGluamVjdCxcclxuICBPbkRlc3Ryb3ksXHJcbiAgRWxlbWVudFJlZixcclxuICBWaWV3Q2hpbGQsXHJcbiAgQWZ0ZXJWaWV3SW5pdCxcclxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgSHR0cENsaWVudCwgSHR0cEhlYWRlcnMgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XHJcbmltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XHJcbmltcG9ydCB7IEZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xyXG5pbXBvcnQgeyBTdWJqZWN0IH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcclxuXHJcbi8qKlxyXG4gKiBSYXRpbmcgZW51bSBtYXRjaGluZyB0aGUgYmFja2VuZFxyXG4gKi9cclxuZXhwb3J0IGVudW0gUmF0aW5nIHtcclxuICBOb1JhdGluZyA9ICdObyByYXRpbmcnLFxyXG4gIFdlYWsgPSAnV2VhaycsXHJcbiAgU3VmZmljaWVudCA9ICdTdWZmaWNpZW50JyxcclxuICBTdHJvbmcgPSAnU3Ryb25nJyxcclxufVxyXG5cclxuLyoqXHJcbiAqIENvbmZpZ3VyYXRpb24gaW50ZXJmYWNlIGZvciB0aGUgRm9ybVF1ZXN0aW9uQ29tcG9uZW50XHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIEZvcm1RdWVzdGlvbkNvbmZpZyB7XHJcbiAgLyoqIEFQSSBrZXkgZm9yIGF1dGhlbnRpY2F0aW9uICovXHJcbiAgYXBpS2V5OiBzdHJpbmc7XHJcbiAgLyoqIEJhc2UgVVJMIG9mIHRoZSBBUEkgZW5kcG9pbnQgKi9cclxuICBlbmRwb2ludDogc3RyaW5nO1xyXG4gIC8qKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHF1ZXN0aW9uICovXHJcbiAgcXVlc3Rpb25JZDogc3RyaW5nO1xyXG4gIC8qKiBUaGUgYW5zd2VyIHRleHQgKGhhbmRsZWQgaW50ZXJuYWxseSkgKi9cclxuICBhbnN3ZXJUZXh0OiBzdHJpbmc7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBBUEkgcmVzcG9uc2UgaW50ZXJmYWNlIGZvciBmb3JtIHN1Ym1pc3Npb25zXHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIEFwaVJlc3BvbnNlIHtcclxuICAvKiogV2hldGhlciB0aGUgc3VibWlzc2lvbiB3YXMgc3VjY2Vzc2Z1bCAqL1xyXG4gIHN1Y2Nlc3M6IGJvb2xlYW47XHJcbiAgLyoqIE9wdGlvbmFsIGVycm9yIG1lc3NhZ2UgKi9cclxuICBlcnJvcj86IHN0cmluZztcclxuICAvKiogQUkgZmVlZGJhY2sgdGV4dCAqL1xyXG4gIGZlZWRiYWNrVGV4dD86IHN0cmluZztcclxuICAvKiogQUkgcmF0aW5nICovXHJcbiAgcmF0aW5nPzogUmF0aW5nO1xyXG59XHJcblxyXG5AQ29tcG9uZW50KHtcclxuICBzZWxlY3RvcjogJ2FwcC1mb3JtLXF1ZXN0aW9uJyxcclxuICB0ZW1wbGF0ZTogYFxyXG4gICAgPGRpdiBjbGFzcz1cImZvcm0tcXVlc3Rpb24tY29udGFpbmVyXCI+XHJcbiAgICAgIDwhLS0gSGlkZGVuIGF1ZGlvIGVsZW1lbnQgZm9yIHN0cmVhbWluZyBhdWRpbyAtLT5cclxuICAgICAgPGF1ZGlvICNob3ZlckF1ZGlvIHByZWxvYWQ9XCJub25lXCI+PC9hdWRpbz5cclxuXHJcbiAgICAgIDxkaXYgY2xhc3M9XCJmb3JtLWZpZWxkXCI+XHJcbiAgICAgICAgPGRpdiBjbGFzcz1cInRleHRhcmVhLWNvbnRhaW5lclwiPlxyXG4gICAgICAgICAgPHRleHRhcmVhXHJcbiAgICAgICAgICAgIGlkPVwiYW5zd2VyLXRleHRhcmVhXCJcclxuICAgICAgICAgICAgWyhuZ01vZGVsKV09XCJhbnN3ZXJUZXh0XCJcclxuICAgICAgICAgICAgKGJsdXIpPVwib25CbHVyKClcIlxyXG4gICAgICAgICAgICAoaW5wdXQpPVwib25JbnB1dCgpXCJcclxuICAgICAgICAgICAgcm93cz1cIjNcIlxyXG4gICAgICAgICAgICBwbGFjZWhvbGRlcj1cIlR5cGUgeW91ciBhbnN3ZXIgaGVyZS4uLlwiXHJcbiAgICAgICAgICAgIGNsYXNzPVwiYW5zd2VyLXRleHRhcmVhXCJcclxuICAgICAgICAgID5cclxuICAgICAgICAgIDwvdGV4dGFyZWE+XHJcblxyXG4gICAgICAgICAgPCEtLSBSYXRpbmcgYmFyIC0gYWx3YXlzIHZpc2libGUgKHNob3dzIGJyYW5kaW5nIHdoZW4gZW1wdHksIHJhdGluZyB3aGVuIGhhcyBjb250ZW50KSAtLT5cclxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJyYXRpbmctY29udGFpbmVyXCI+XHJcbiAgICAgICAgICAgIDwhLS0gRWRpdCBpY29uIGZvciB0ZXh0IGNsZWFudXAgLS0+XHJcbiAgICAgICAgICAgIDxidXR0b25cclxuICAgICAgICAgICAgICAqbmdJZj1cIiFpc0NsZWFuaW5nXCJcclxuICAgICAgICAgICAgICBjbGFzcz1cImVkaXQtaWNvblwiXHJcbiAgICAgICAgICAgICAgW2NsYXNzLmRpc2FibGVkXT1cIiFhbnN3ZXJUZXh0LnRyaW0oKVwiXHJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cImNsZWFudXBUZXh0KClcIlxyXG4gICAgICAgICAgICAgIFt0aXRsZV09XCJcclxuICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpXHJcbiAgICAgICAgICAgICAgICAgID8gJ0ZpeCBzcGVsbGluZyBhbmQgZ3JhbW1hciB3aXRoIEFJJ1xyXG4gICAgICAgICAgICAgICAgICA6ICdBZGQgdGV4dCB0byBlbmFibGUgQUkgZWRpdGluZydcclxuICAgICAgICAgICAgICBcIlxyXG4gICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAg4pyP77iPXHJcbiAgICAgICAgICAgIDwvYnV0dG9uPlxyXG4gICAgICAgICAgICA8ZGl2XHJcbiAgICAgICAgICAgICAgKm5nSWY9XCJpc0NsZWFuaW5nXCJcclxuICAgICAgICAgICAgICBjbGFzcz1cImNsZWFuaW5nLXNwaW5uZXJcIlxyXG4gICAgICAgICAgICAgIHRpdGxlPVwiQUkgaXMgZml4aW5nIHlvdXIgdGV4dC4uLlwiXHJcbiAgICAgICAgICAgID5cclxuICAgICAgICAgICAgICDinKhcclxuICAgICAgICAgICAgPC9kaXY+XHJcbiAgICAgICAgICAgIDxkaXZcclxuICAgICAgICAgICAgICBjbGFzcz1cImZlZWRiYWNrLWJhclwiXHJcbiAgICAgICAgICAgICAgKG1vdXNlZW50ZXIpPVwib25SYXRpbmdCYXJIb3ZlcigpXCJcclxuICAgICAgICAgICAgICAobW91c2VsZWF2ZSk9XCJvblJhdGluZ0JhckxlYXZlKClcIlxyXG4gICAgICAgICAgICAgIFtuZ0NsYXNzXT1cIntcclxuICAgICAgICAgICAgICAgIG91dGxpbmU6XHJcbiAgICAgICAgICAgICAgICAgICFyYXRpbmcgfHwgcmF0aW5nID09PSAnTm8gcmF0aW5nJyB8fCAhYW5zd2VyVGV4dC50cmltKCksXHJcbiAgICAgICAgICAgICAgICB3ZWFrOlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgPT09ICdXZWFrJyAmJlxyXG4gICAgICAgICAgICAgICAgICBhbnN3ZXJUZXh0LnRyaW0oKSAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNUeXBpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzU3VibWl0dGluZyxcclxuICAgICAgICAgICAgICAgIHN1ZmZpY2llbnQ6XHJcbiAgICAgICAgICAgICAgICAgIHJhdGluZyA9PT0gJ1N1ZmZpY2llbnQnICYmXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1R5cGluZyAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNTdWJtaXR0aW5nLFxyXG4gICAgICAgICAgICAgICAgc3Ryb25nOlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgPT09ICdTdHJvbmcnICYmXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1R5cGluZyAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNTdWJtaXR0aW5nLFxyXG4gICAgICAgICAgICAgICAgbm9SYXRpbmc6XHJcbiAgICAgICAgICAgICAgICAgIHJhdGluZyA9PT0gJ05vIHJhdGluZycgJiZcclxuICAgICAgICAgICAgICAgICAgYW5zd2VyVGV4dC50cmltKCkgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzVHlwaW5nICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1N1Ym1pdHRpbmcsXHJcbiAgICAgICAgICAgICAgICBicmFuZGluZzogIWFuc3dlclRleHQudHJpbSgpLFxyXG4gICAgICAgICAgICAgICAgdHlwaW5nOiAoaXNUeXBpbmcgfHwgaXNTdWJtaXR0aW5nKSAmJiBhbnN3ZXJUZXh0LnRyaW0oKSxcclxuICAgICAgICAgICAgICAgIGVycm9yOiBlcnJvck1lc3NhZ2UgJiYgYW5zd2VyVGV4dC50cmltKClcclxuICAgICAgICAgICAgICB9XCJcclxuICAgICAgICAgICAgPlxyXG4gICAgICAgICAgICAgIDxzcGFuXHJcbiAgICAgICAgICAgICAgICBjbGFzcz1cInJhdGluZy1sYWJlbFwiXHJcbiAgICAgICAgICAgICAgICAqbmdJZj1cIlxyXG4gICAgICAgICAgICAgICAgICBhbnN3ZXJUZXh0LnRyaW0oKSAmJlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzVHlwaW5nICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1N1Ym1pdHRpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWVycm9yTWVzc2FnZVxyXG4gICAgICAgICAgICAgICAgXCJcclxuICAgICAgICAgICAgICAgID57eyByYXRpbmcgfX08L3NwYW5cclxuICAgICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAgPHNwYW5cclxuICAgICAgICAgICAgICAgIGNsYXNzPVwidHlwaW5nLWxhYmVsXCJcclxuICAgICAgICAgICAgICAgICpuZ0lmPVwiXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgIChpc1R5cGluZyB8fCBpc1N1Ym1pdHRpbmcpICYmXHJcbiAgICAgICAgICAgICAgICAgICFlcnJvck1lc3NhZ2VcclxuICAgICAgICAgICAgICAgIFwiXHJcbiAgICAgICAgICAgICAgICA+PHNwYW4gY2xhc3M9XCJkb3QxXCI+Ljwvc3Bhbj48c3BhbiBjbGFzcz1cImRvdDJcIj4uPC9zcGFuXHJcbiAgICAgICAgICAgICAgICA+PHNwYW4gY2xhc3M9XCJkb3QzXCI+Ljwvc3Bhbj48L3NwYW5cclxuICAgICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAgPHNwYW5cclxuICAgICAgICAgICAgICAgIGNsYXNzPVwiZXJyb3ItbGFiZWxcIlxyXG4gICAgICAgICAgICAgICAgKm5nSWY9XCJhbnN3ZXJUZXh0LnRyaW0oKSAmJiBlcnJvck1lc3NhZ2VcIlxyXG4gICAgICAgICAgICAgICAgPkVycm9yPC9zcGFuXHJcbiAgICAgICAgICAgICAgPlxyXG4gICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiYnJhbmRpbmctbGFiZWxcIiAqbmdJZj1cIiFhbnN3ZXJUZXh0LnRyaW0oKVwiXHJcbiAgICAgICAgICAgICAgICA+QW5zd2VyUGVyZmVjdCBBSVxyXG4gICAgICAgICAgICAgICAgPCEtLSBCcmFuZGluZyB0b29sdGlwIC0tPlxyXG4gICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImJyYW5kaW5nLXRvb2x0aXBcIj5cclxuICAgICAgICAgICAgICAgICAgQUktcG93ZXJlZCBmZWVkYmFjayB0byBoZWxwIHlvdSB3cml0ZSBiZXR0ZXIgYW5zd2Vyc1xyXG4gICAgICAgICAgICAgICAgPC9kaXY+XHJcbiAgICAgICAgICAgICAgPC9zcGFuPlxyXG4gICAgICAgICAgICAgIDwhLS0gVG9vbHRpcCBmZWVkYmFjayB0ZXh0IG9uIGhvdmVyIChvbmx5IHNob3cgaWYgZmVlZGJhY2sgZXhpc3RzKSAtLT5cclxuICAgICAgICAgICAgICA8ZGl2XHJcbiAgICAgICAgICAgICAgICBjbGFzcz1cImZlZWRiYWNrLXRvb2x0aXBcIlxyXG4gICAgICAgICAgICAgICAgKm5nSWY9XCJmZWVkYmFja1RleHQgJiYgYW5zd2VyVGV4dC50cmltKClcIlxyXG4gICAgICAgICAgICAgID5cclxuICAgICAgICAgICAgICAgIHt7IGZlZWRiYWNrVGV4dCB9fVxyXG4gICAgICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgICAgICA8L2Rpdj5cclxuICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgIDwvZGl2PlxyXG4gICAgICA8L2Rpdj5cclxuICAgIDwvZGl2PlxyXG4gIGAsXHJcbiAgc3R5bGVzOiBbXHJcbiAgICBgXHJcbiAgICAgIC5mb3JtLXF1ZXN0aW9uLWNvbnRhaW5lciB7XHJcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMXJlbTtcclxuICAgICAgfVxyXG5cclxuICAgICAgLmZvcm0tZmllbGQge1xyXG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XHJcbiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcclxuICAgICAgICBtYXJnaW4tYm90dG9tOiAwLjVyZW07XHJcbiAgICAgIH1cclxuXHJcbiAgICAgIC50ZXh0YXJlYS1jb250YWluZXIge1xyXG4gICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcclxuICAgICAgICBkaXNwbGF5OiBibG9jaztcclxuICAgICAgfVxyXG5cclxuICAgICAgLmFuc3dlci10ZXh0YXJlYSB7XHJcbiAgICAgICAgd2lkdGg6IDEwMCU7XHJcbiAgICAgICAgcGFkZGluZzogMC43NXJlbTtcclxuICAgICAgICBib3JkZ