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
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS1xdWVzdGlvbi5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZm9ybS1xdWVzdGlvbi5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFDVCxLQUFLLEVBRUwsTUFBTSxFQUVOLFVBQVUsRUFDVixTQUFTLEdBRVYsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMvRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDL0IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGdCQUFnQixDQUFDOzs7O0FBRTNDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksTUFLWDtBQUxELFdBQVksTUFBTTtJQUNoQixnQ0FBc0IsQ0FBQTtJQUN0Qix1QkFBYSxDQUFBO0lBQ2IsbUNBQXlCLENBQUE7SUFDekIsMkJBQWlCLENBQUE7QUFDbkIsQ0FBQyxFQUxXLE1BQU0sS0FBTixNQUFNLFFBS2pCO0FBMGNEOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLHFCQUFxQjtJQWxibEM7UUFtYm1CLFNBQUksR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7UUFXM0MsZ0VBQWdFO1FBQ3ZELGdCQUFXLEdBQVksSUFBSSxDQUFDO1FBTXJDLGVBQVUsR0FBVyxFQUFFLENBQUM7UUFDeEIsaUJBQVksR0FBWSxLQUFLLENBQUM7UUFDOUIsaUJBQVksR0FBVyxFQUFFLENBQUM7UUFDMUIsaUJBQVksR0FBVyxFQUFFLENBQUM7UUFDMUIsV0FBTSxHQUFrQixJQUFJLENBQUM7UUFDN0IsYUFBUSxHQUFZLEtBQUssQ0FBQztRQUMxQixlQUFV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LFVBQVU7c0JBRFQsU0FBUzt1QkFBQyxZQUFZLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcclxuICBDb21wb25lbnQsXHJcbiAgSW5wdXQsXHJcbiAgT25Jbml0LFxyXG4gIGluamVjdCxcclxuICBPbkRlc3Ryb3ksXHJcbiAgRWxlbWVudFJlZixcclxuICBWaWV3Q2hpbGQsXHJcbiAgQWZ0ZXJWaWV3SW5pdCxcclxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgSHR0cENsaWVudCwgSHR0cEhlYWRlcnMgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XHJcbmltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XHJcbmltcG9ydCB7IEZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xyXG5pbXBvcnQgeyBTdWJqZWN0IH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcclxuXHJcbi8qKlxyXG4gKiBSYXRpbmcgZW51bSBtYXRjaGluZyB0aGUgYmFja2VuZFxyXG4gKi9cclxuZXhwb3J0IGVudW0gUmF0aW5nIHtcclxuICBOb1JhdGluZyA9ICdObyByYXRpbmcnLFxyXG4gIFdlYWsgPSAnV2VhaycsXHJcbiAgU3VmZmljaWVudCA9ICdTdWZmaWNpZW50JyxcclxuICBTdHJvbmcgPSAnU3Ryb25nJyxcclxufVxyXG5cclxuLyoqXHJcbiAqIENvbmZpZ3VyYXRpb24gaW50ZXJmYWNlIGZvciB0aGUgRm9ybVF1ZXN0aW9uQ29tcG9uZW50XHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIEZvcm1RdWVzdGlvbkNvbmZpZyB7XHJcbiAgLyoqIEFQSSBrZXkgZm9yIGF1dGhlbnRpY2F0aW9uICovXHJcbiAgYXBpS2V5OiBzdHJpbmc7XHJcbiAgLyoqIEJhc2UgVVJMIG9mIHRoZSBBUEkgZW5kcG9pbnQgKi9cclxuICBlbmRwb2ludDogc3RyaW5nO1xyXG4gIC8qKiBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHF1ZXN0aW9uICovXHJcbiAgcXVlc3Rpb25JZDogc3RyaW5nO1xyXG4gIC8qKiBUaGUgYW5zd2VyIHRleHQgKGhhbmRsZWQgaW50ZXJuYWxseSkgKi9cclxuICBhbnN3ZXJUZXh0OiBzdHJpbmc7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBBUEkgcmVzcG9uc2UgaW50ZXJmYWNlIGZvciBmb3JtIHN1Ym1pc3Npb25zXHJcbiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIEFwaVJlc3BvbnNlIHtcclxuICAvKiogV2hldGhlciB0aGUgc3VibWlzc2lvbiB3YXMgc3VjY2Vzc2Z1bCAqL1xyXG4gIHN1Y2Nlc3M6IGJvb2xlYW47XHJcbiAgLyoqIE9wdGlvbmFsIGVycm9yIG1lc3NhZ2UgKi9cclxuICBlcnJvcj86IHN0cmluZztcclxuICAvKiogQUkgZmVlZGJhY2sgdGV4dCAqL1xyXG4gIGZlZWRiYWNrVGV4dD86IHN0cmluZztcclxuICAvKiogQUkgcmF0aW5nICovXHJcbiAgcmF0aW5nPzogUmF0aW5nO1xyXG59XHJcblxyXG5AQ29tcG9uZW50KHtcclxuICBzZWxlY3RvcjogJ2FwcC1mb3JtLXF1ZXN0aW9uJyxcclxuICB0ZW1wbGF0ZTogYFxyXG4gICAgPGRpdiBjbGFzcz1cImZvcm0tcXVlc3Rpb24tY29udGFpbmVyXCI+XHJcbiAgICAgIDwhLS0gSGlkZGVuIGF1ZGlvIGVsZW1lbnQgZm9yIHN0cmVhbWluZyBhdWRpbyAtLT5cclxuICAgICAgPGF1ZGlvICNob3ZlckF1ZGlvIHByZWxvYWQ9XCJub25lXCI+PC9hdWRpbz5cclxuXHJcbiAgICAgIDxkaXYgY2xhc3M9XCJmb3JtLWZpZWxkXCI+XHJcbiAgICAgICAgPGRpdiBjbGFzcz1cInRleHRhcmVhLWNvbnRhaW5lclwiPlxyXG4gICAgICAgICAgPHRleHRhcmVhXHJcbiAgICAgICAgICAgIGlkPVwiYW5zd2VyLXRleHRhcmVhXCJcclxuICAgICAgICAgICAgWyhuZ01vZGVsKV09XCJhbnN3ZXJUZXh0XCJcclxuICAgICAgICAgICAgKGJsdXIpPVwib25CbHVyKClcIlxyXG4gICAgICAgICAgICAoaW5wdXQpPVwib25JbnB1dCgpXCJcclxuICAgICAgICAgICAgcm93cz1cIjNcIlxyXG4gICAgICAgICAgICBwbGFjZWhvbGRlcj1cIlR5cGUgeW91ciBhbnN3ZXIgaGVyZS4uLlwiXHJcbiAgICAgICAgICAgIGNsYXNzPVwiYW5zd2VyLXRleHRhcmVhXCJcclxuICAgICAgICAgID5cclxuICAgICAgICAgIDwvdGV4dGFyZWE+XHJcblxyXG4gICAgICAgICAgPCEtLSBSYXRpbmcgYmFyIC0gYWx3YXlzIHZpc2libGUgKHNob3dzIGJyYW5kaW5nIHdoZW4gZW1wdHksIHJhdGluZyB3aGVuIGhhcyBjb250ZW50KSAtLT5cclxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJyYXRpbmctY29udGFpbmVyXCI+XHJcbiAgICAgICAgICAgIDwhLS0gRWRpdCBpY29uIGZvciB0ZXh0IGNsZWFudXAgLS0+XHJcbiAgICAgICAgICAgIDxidXR0b25cclxuICAgICAgICAgICAgICAqbmdJZj1cIiFpc0NsZWFuaW5nXCJcclxuICAgICAgICAgICAgICBjbGFzcz1cImVkaXQtaWNvblwiXHJcbiAgICAgICAgICAgICAgW2NsYXNzLmRpc2FibGVkXT1cIiFhbnN3ZXJUZXh0LnRyaW0oKVwiXHJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cImNsZWFudXBUZXh0KClcIlxyXG4gICAgICAgICAgICAgIFt0aXRsZV09XCJcclxuICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpXHJcbiAgICAgICAgICAgICAgICAgID8gJ0ZpeCBzcGVsbGluZyBhbmQgZ3JhbW1hciB3aXRoIEFJJ1xyXG4gICAgICAgICAgICAgICAgICA6ICdBZGQgdGV4dCB0byBlbmFibGUgQUkgZWRpdGluZydcclxuICAgICAgICAgICAgICBcIlxyXG4gICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAg4pyP77iPXHJcbiAgICAgICAgICAgIDwvYnV0dG9uPlxyXG4gICAgICAgICAgICA8ZGl2XHJcbiAgICAgICAgICAgICAgKm5nSWY9XCJpc0NsZWFuaW5nXCJcclxuICAgICAgICAgICAgICBjbGFzcz1cImNsZWFuaW5nLXNwaW5uZXJcIlxyXG4gICAgICAgICAgICAgIHRpdGxlPVwiQUkgaXMgZml4aW5nIHlvdXIgdGV4dC4uLlwiXHJcbiAgICAgICAgICAgID5cclxuICAgICAgICAgICAgICDinKhcclxuICAgICAgICAgICAgPC9kaXY+XHJcbiAgICAgICAgICAgIDxkaXZcclxuICAgICAgICAgICAgICBjbGFzcz1cImZlZWRiYWNrLWJhclwiXHJcbiAgICAgICAgICAgICAgKG1vdXNlZW50ZXIpPVwib25SYXRpbmdCYXJIb3ZlcigpXCJcclxuICAgICAgICAgICAgICAobW91c2VsZWF2ZSk9XCJvblJhdGluZ0JhckxlYXZlKClcIlxyXG4gICAgICAgICAgICAgIFtuZ0NsYXNzXT1cIntcclxuICAgICAgICAgICAgICAgIG91dGxpbmU6XHJcbiAgICAgICAgICAgICAgICAgICFyYXRpbmcgfHwgcmF0aW5nID09PSAnTm8gcmF0aW5nJyB8fCAhYW5zd2VyVGV4dC50cmltKCksXHJcbiAgICAgICAgICAgICAgICB3ZWFrOlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgPT09ICdXZWFrJyAmJlxyXG4gICAgICAgICAgICAgICAgICBhbnN3ZXJUZXh0LnRyaW0oKSAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNUeXBpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzU3VibWl0dGluZyxcclxuICAgICAgICAgICAgICAgIHN1ZmZpY2llbnQ6XHJcbiAgICAgICAgICAgICAgICAgIHJhdGluZyA9PT0gJ1N1ZmZpY2llbnQnICYmXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1R5cGluZyAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNTdWJtaXR0aW5nLFxyXG4gICAgICAgICAgICAgICAgc3Ryb25nOlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgPT09ICdTdHJvbmcnICYmXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1R5cGluZyAmJlxyXG4gICAgICAgICAgICAgICAgICAhaXNTdWJtaXR0aW5nLFxyXG4gICAgICAgICAgICAgICAgbm9SYXRpbmc6XHJcbiAgICAgICAgICAgICAgICAgIHJhdGluZyA9PT0gJ05vIHJhdGluZycgJiZcclxuICAgICAgICAgICAgICAgICAgYW5zd2VyVGV4dC50cmltKCkgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzVHlwaW5nICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1N1Ym1pdHRpbmcsXHJcbiAgICAgICAgICAgICAgICBicmFuZGluZzogIWFuc3dlclRleHQudHJpbSgpLFxyXG4gICAgICAgICAgICAgICAgdHlwaW5nOiAoaXNUeXBpbmcgfHwgaXNTdWJtaXR0aW5nKSAmJiBhbnN3ZXJUZXh0LnRyaW0oKSxcclxuICAgICAgICAgICAgICAgIGVycm9yOiBlcnJvck1lc3NhZ2UgJiYgYW5zd2VyVGV4dC50cmltKClcclxuICAgICAgICAgICAgICB9XCJcclxuICAgICAgICAgICAgPlxyXG4gICAgICAgICAgICAgIDxzcGFuXHJcbiAgICAgICAgICAgICAgICBjbGFzcz1cInJhdGluZy1sYWJlbFwiXHJcbiAgICAgICAgICAgICAgICAqbmdJZj1cIlxyXG4gICAgICAgICAgICAgICAgICBhbnN3ZXJUZXh0LnRyaW0oKSAmJlxyXG4gICAgICAgICAgICAgICAgICByYXRpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWlzVHlwaW5nICYmXHJcbiAgICAgICAgICAgICAgICAgICFpc1N1Ym1pdHRpbmcgJiZcclxuICAgICAgICAgICAgICAgICAgIWVycm9yTWVzc2FnZVxyXG4gICAgICAgICAgICAgICAgXCJcclxuICAgICAgICAgICAgICAgID57eyByYXRpbmcgfX08L3NwYW5cclxuICAgICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAgPHNwYW5cclxuICAgICAgICAgICAgICAgIGNsYXNzPVwidHlwaW5nLWxhYmVsXCJcclxuICAgICAgICAgICAgICAgICpuZ0lmPVwiXHJcbiAgICAgICAgICAgICAgICAgIGFuc3dlclRleHQudHJpbSgpICYmXHJcbiAgICAgICAgICAgICAgICAgIChpc1R5cGluZyB8fCBpc1N1Ym1pdHRpbmcpICYmXHJcbiAgICAgICAgICAgICAgICAgICFlcnJvck1lc3NhZ2VcclxuICAgICAgICAgICAgICAgIFwiXHJcbiAgICAgICAgICAgICAgICA+PHNwYW4gY2xhc3M9XCJkb3QxXCI+Ljwvc3Bhbj48c3BhbiBjbGFzcz1cImRvdDJcIj4uPC9zcGFuXHJcbiAgICAgICAgICAgICAgICA+PHNwYW4gY2xhc3M9XCJkb3QzXCI+Ljwvc3Bhbj48L3NwYW5cclxuICAgICAgICAgICAgICA+XHJcbiAgICAgICAgICAgICAgPHNwYW5cclxuICAgICAgICAgICAgICAgIGNsYXNzPVwiZXJyb3ItbGFiZWxcIlxyXG4gICAgICAgICAgICAgICAgKm5nSWY9XCJhbnN3ZXJUZXh0LnRyaW0oKSAmJiBlcnJvck1lc3NhZ2VcIlxyXG4gICAgICAgICAgICAgICAgPkVycm9yPC9zcGFuXHJcbiAgICAgICAgICAgICAgPlxyXG4gICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiYnJhbmRpbmctbGFiZWxcIiAqbmdJZj1cIiFhbnN3ZXJUZXh0LnRyaW0oKVwiXHJcbiAgICAgICAgICAgICAgICA+QW5zd2VyUGVyZmVjdCBBSVxyXG4gICAgICAgICAgICAgICAgPCEtLSBCcmFuZGluZyB0b29sdGlwIC0tPlxyXG4gICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImJyYW5kaW5nLXRvb2x0aXBcIj5cclxuICAgICAgICAgICAgICAgICAgQUktcG93ZXJlZCBmZWVkYmFjayB0byBoZWxwIHlvdSB3cml0ZSBiZXR0ZXIgYW5zd2Vyc1xyXG4gICAgICAgICAgICAgICAgPC9kaXY+XHJcbiAgICAgICAgICAgICAgPC9zcGFuPlxyXG4gICAgICAgICAgICAgIDwhLS0gVG9vbHRpcCBmZWVkYmFjayB0ZXh0IG9uIGhvdmVyIChvbmx5IHNob3cgaWYgZmVlZGJhY2sgZXhpc3RzKSAtLT5cclxuICAgICAgICAgICAgICA8ZGl2XHJcbiAgICAgICAgICAgICAgICBjbGFzcz1cImZlZWRiYWNrLXRvb2x0aXBcIlxyXG4gICAgICAgICAgICAgICAgKm5nSWY9XCJmZWVkYmFja1RleHQgJiYgYW5zd2VyVGV4dC50cmltKClcIlxyXG4gICAgICAgICAgICAgID5cclxuICAgICAgICAgICAgICAgIHt7IGZlZWRiYWNrVGV4dCB9fVxyXG4gICAgICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgICAgICA8L2Rpdj5cclxuICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgIDwvZGl2PlxyXG4gICAgICA8L2Rpdj5cclxuICAgIDwvZGl2PlxyXG4gIGAsXHJcbiAgc3R5bGVzOiBbXHJcbiAgICBgXHJcbiAgICAgIC5mb3JtLXF1ZXN0aW9uLWNvbnRhaW5lciB7XHJcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMXJlbTtcclxuICAgICAgfVxyXG5cclxuICAgICAgLmZvcm0tZmllbGQge1xyXG4gICAgICAgIGRpc3BsYXk6IGZsZXg7XHJcbiAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcclxuICAgICAgICBtYXJnaW4tYm90dG9tOiAwLjVyZW07XHJcbiAgICAgIH1cclxuXHJcbiAgICAgIC50ZXh0YXJlYS1jb250YWluZXIge1xyXG4gICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcclxuICAgICAgICBkaXNwbGF5OiBibG9jaztcclxuICAgICAgfVxyXG5cclxuICAgICAgLmFuc3dlci10ZXh0YXJlYSB7XHJcbiAgICAgICAgd2lkdGg6IDEwMCU7XHJcbiAgICAgICAgcGFkZGluZzogMC43NXJlbTtcclxuICAgICAgICBib3JkZ