UNPKG

speech-to-element

Version:

Add real-time speech to text functionality into your website with no effort

229 lines (228 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Speech = void 0; const eventListeners_1 = require("./utils/eventListeners"); const preResultUtils_1 = require("./utils/preResultUtils"); const commandUtils_1 = require("./utils/commandUtils"); const autoScroll_1 = require("./utils/autoScroll"); const highlight_1 = require("./utils/highlight"); const elements_1 = require("./utils/elements"); const padding_1 = require("./utils/padding"); const browser_1 = require("./utils/browser"); const cursor_1 = require("./utils/cursor"); const text_1 = require("./utils/text"); class Speech { constructor() { this.finalTranscript = ''; // used for editable element this.interimSpan = elements_1.Elements.createInterimSpan(); this.finalSpan = elements_1.Elements.createGenericSpan(); // used for allowing autoScroll() to scroll to it when interimSpan enters another line and doesn't scroll this.scrollingSpan = elements_1.Elements.createGenericSpan(); this.isCursorAtEnd = false; this.spansPopulated = false; this.startPadding = ''; // primitive elements use this as the right hand side text of cursor this.endPadding = ''; this.numberOfSpacesBeforeNewText = 0; // primarily used for setting cursor for primitive elements this.numberOfSpacesAfterNewText = 0; // primarily used for setting cursor for primitive elements this.isHighlighted = false; this.primitiveTextRecorded = false; this.recognizing = false; this._displayInterimResults = true; this.insertInCursorLocation = true; this.autoScroll = true; this.isRestarting = false; this.isPaused = false; this.isWaitingForCommand = false; this.isTargetInShadow = false; this.cannotBeStopped = false; // this is mostly used for Azure to prevent user from stopping when it is connecting this.resetState(); } prepareBeforeStart(options) { var _a, _b; if (options === null || options === void 0 ? void 0 : options.element) { eventListeners_1.EventListeners.add(this, options); if (Array.isArray(options.element)) { // checks if any of available elements are currently focused, else proceeds to the first const focusedElement = options.element.find((element) => element === document.activeElement); const targetElement = focusedElement || options.element[0]; if (!targetElement) return; this.prepare(targetElement); } else { this.prepare(options.element); } } if ((options === null || options === void 0 ? void 0 : options.displayInterimResults) !== undefined) this._displayInterimResults = options.displayInterimResults; if (options === null || options === void 0 ? void 0 : options.textColor) { this._finalTextColor = (_a = options === null || options === void 0 ? void 0 : options.textColor) === null || _a === void 0 ? void 0 : _a.final; elements_1.Elements.applyCustomColors(this, options.textColor); } if ((options === null || options === void 0 ? void 0 : options.insertInCursorLocation) !== undefined) this.insertInCursorLocation = options.insertInCursorLocation; if ((options === null || options === void 0 ? void 0 : options.autoScroll) !== undefined) this.autoScroll = options.autoScroll; this._onResult = options === null || options === void 0 ? void 0 : options.onResult; this._onPreResult = options === null || options === void 0 ? void 0 : options.onPreResult; this._onStart = options === null || options === void 0 ? void 0 : options.onStart; this._onStop = options === null || options === void 0 ? void 0 : options.onStop; this._onError = options === null || options === void 0 ? void 0 : options.onError; this.onCommandModeTrigger = options === null || options === void 0 ? void 0 : options.onCommandModeTrigger; this.onPauseTrigger = options === null || options === void 0 ? void 0 : options.onPauseTrigger; this._options = options; if ((_b = this._options) === null || _b === void 0 ? void 0 : _b.commands) this.commands = commandUtils_1.CommandUtils.process(this._options.commands); } prepare(targetElement) { padding_1.Padding.setState(this, targetElement); highlight_1.Highlight.setState(this, targetElement); this.isTargetInShadow = elements_1.Elements.isInsideShadowDOM(targetElement); if (elements_1.Elements.isPrimitiveElement(targetElement)) { this._primitiveElement = targetElement; this._originalText = this._primitiveElement.value; } else { this._genericElement = targetElement; this._originalText = this._genericElement.textContent; } } // there was an attempt to optimize this by not having to restart the service and just reset state: // unfortunately it did not work because the service would still continue firing the intermediate and final results // into the new position resetRecording(options) { this.isRestarting = true; this.stop(true); this.resetState(true); this.start(options, true); } // prettier-ignore updateElements(interimTranscript, finalTranscript, newText) { var _a; const newFinalText = text_1.Text.capitalize(finalTranscript); if (this.finalTranscript === newFinalText && interimTranscript === '') return; if (preResultUtils_1.PreResultUtils.process(this, newText, interimTranscript === '', this._onPreResult, this._options)) { interimTranscript = '', newText = ''; } const commandResult = this.commands && commandUtils_1.CommandUtils.execCommand(this, newText, this._options, this._primitiveElement || this._genericElement, this._originalText); if (commandResult) { if (commandResult.doNotProcessTranscription) return; interimTranscript = '', newText = ''; } if (this.isPaused || this.isWaitingForCommand) return; (_a = this._onResult) === null || _a === void 0 ? void 0 : _a.call(this, newText, interimTranscript === ''); this.finalTranscript = newFinalText; if (!this._displayInterimResults) interimTranscript = ''; // this is primarily used to remove padding when interim/final text is removed on command const isNoText = this.finalTranscript === '' && interimTranscript === ''; if (this._primitiveElement) { this.updatePrimitiveElement(this._primitiveElement, interimTranscript, isNoText); } else if (this._genericElement) { this.updateGenericElement(this._genericElement, interimTranscript, isNoText); } } // prettier-ignore // remember that padding values here contain actual text left and right updatePrimitiveElement(element, interimTranscript, isNoText) { if (this.isHighlighted) highlight_1.Highlight.removeForPrimitive(this, element); if (!this.primitiveTextRecorded) padding_1.Padding.adjustStateAfterRecodingPrimitiveElement(this, element); if (isNoText) padding_1.Padding.adjustSateForNoTextPrimitiveElement(this); const cursorLeftSideText = this.startPadding + this.finalTranscript + interimTranscript; element.value = cursorLeftSideText + this.endPadding; if (!this.isTargetInShadow) { const newCusrorPos = cursorLeftSideText.length + this.numberOfSpacesAfterNewText; cursor_1.Cursor.setOffsetForPrimitive(element, newCusrorPos, this.autoScroll); } if (this.autoScroll && browser_1.Browser.IS_SAFARI() && this.isCursorAtEnd) autoScroll_1.AutoScroll.scrollSafariPrimitiveToEnd(element); } updateGenericElement(element, interimTranscript, isNoText) { if (this.isHighlighted) highlight_1.Highlight.removeForGeneric(this, element); if (!this.spansPopulated) elements_1.Elements.appendSpans(this, element); // for web speech api - safari only returns final text - no interim const finalText = (isNoText ? '' : this.startPadding) + text_1.Text.lineBreak(this.finalTranscript); this.finalSpan.innerHTML = finalText; const isAutoScrollingRequired = autoScroll_1.AutoScroll.isRequired(this.autoScroll, element); autoScroll_1.AutoScroll.changeStateIfNeeded(this, isAutoScrollingRequired); const interimText = text_1.Text.lineBreak(interimTranscript) + (isNoText ? '' : this.endPadding); this.interimSpan.innerHTML = interimText; if (browser_1.Browser.IS_SAFARI() && this.insertInCursorLocation) { cursor_1.Cursor.setOffsetForSafariGeneric(element, finalText.length + interimText.length); } if (isAutoScrollingRequired) autoScroll_1.AutoScroll.scrollGeneric(this, element); if (isNoText) this.scrollingSpan.innerHTML = ''; } finalise(isDuringReset) { if (this._genericElement) { if (isDuringReset) { this.finalSpan = elements_1.Elements.createGenericSpan(); this.setInterimColorToFinal(); this.interimSpan = elements_1.Elements.createInterimSpan(); this.scrollingSpan = elements_1.Elements.createGenericSpan(); } else { this._genericElement.textContent = this._genericElement.textContent; } this.spansPopulated = false; } eventListeners_1.EventListeners.remove(this); } setInterimColorToFinal() { this.interimSpan.style.color = this._finalTextColor || 'black'; } resetState(isDuringReset) { this._primitiveElement = undefined; this._genericElement = undefined; this.finalTranscript = ''; this.finalSpan.innerHTML = ''; this.interimSpan.innerHTML = ''; this.scrollingSpan.innerHTML = ''; this.startPadding = ''; this.endPadding = ''; this.isHighlighted = false; this.primitiveTextRecorded = false; this.numberOfSpacesBeforeNewText = 0; this.numberOfSpacesAfterNewText = 0; if (!isDuringReset) this.stopTimeout = undefined; } setStateOnStart() { var _a; this.recognizing = true; if (this.isRestarting) { // this is the only place where this.isRestarting needs to be set to false // as whn something goes wrong or the user is manually restarting - a new speech service will be initialized this.isRestarting = false; } else { (_a = this._onStart) === null || _a === void 0 ? void 0 : _a.call(this); } } setStateOnStop() { var _a; this.recognizing = false; if (!this.isRestarting) { (_a = this._onStop) === null || _a === void 0 ? void 0 : _a.call(this); } } setStateOnError(details) { var _a; (_a = this._onError) === null || _a === void 0 ? void 0 : _a.call(this, details); this.recognizing = false; } } exports.Speech = Speech;