Changeset 35284
- Timestamp:
- 2021-08-11T16:24:17+12:00 (3 years ago)
- Location:
- main/trunk/model-interfaces-dev/atea
- Files:
-
- 1 added
- 1 deleted
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/model-interfaces-dev/atea/README.md
r35282 r35284 19 19 npx grunt watch 20 20 ``` 21 22 # ASR Development TODO 23 24 - Play audio at certain words, based on timestamps returned by tuhituhi API 25 - Corrections interface 26 - WebVTT download option 27 - Add transcription loading indicator 28 - Make transcriptions fully load in on a separate page, with the normal entry providing an arrow link to expand it on the new page? 29 - Ask user for confirmation before removing a transcription -
main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js
r35282 r35284 4 4 */ 5 5 6 import { TranscribeService, TranscriptionError, TranscriptionModel } from "./TranscribeModule.js"; 7 6 8 /** @type {HTMLAudioElement} */ 7 9 // @ts-ignore … … 20 22 const TRANSCRIPTION_TEMPLATE = document.getElementById("transcriptionTemplate"); 21 23 22 /** @type {HTMLTemplateElement} */23 // @ts-ignore24 const ERROR_TEMPLATE = document.getElementById("errorTemplate");25 26 24 let cachedAudioFileList = new Map(); 27 28 /**29 * @callback loadScriptCallback30 * @param {Event} ev The event31 * @returns {void}32 */33 25 34 26 // Get the size of each character in our monospace font … … 37 29 { 38 30 const monoCharSizeTestElement = document.querySelector(".monospace-font-size"); 39 if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null) { 31 if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null) 32 { 40 33 MONOSPACE_CHAR_SIZE = 8; // Slightly over-estimated guess for size 16 font. 41 34 return; … … 45 38 }); 46 39 47 /** 48 * Loads a remote script. 49 * Found at https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file 50 * 51 * @param {string} url The URL from which to load the script. 52 */ 53 function loadScript(url, callback = () => {}) 54 { 55 var body = document.body; 56 57 var script = document.createElement('script'); 58 script.type = 'text/javascript'; 59 script.src = url; 60 61 script.addEventListener("load", callback); 62 63 body.appendChild(script); 64 } 65 66 var transcribeService;// = new TranscribeService(); 67 loadScript("./interfaces/atea/js/asr/TranscribeService.js", () => { 68 transcribeService = new TranscribeService(); 69 }); 40 var transcribeService = new TranscribeService(); 70 41 71 42 // @ts-ignore 72 43 const AudioUploadComponent = Vue.createApp( 73 { 74 data() 75 { 76 return { 77 files: undefined, 78 canTranscribe: false, 79 isTranscribing: false 80 } 81 }, 82 methods: 83 { 84 openFilePicker() { 85 document.getElementById("audioFileInput")?.click(); 44 { 45 data() 46 { 47 return { 48 files: undefined, 49 canTranscribe: false, 50 isTranscribing: false 51 } 86 52 }, 87 onFilesChanged() 88 { 89 /** @type {HTMLInputElement} */ 90 // @ts-ignore | Object could be null 91 const audioFileInput = document.getElementById("audioFileInput"); 92 93 if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) { 94 this.canTranscribe = true; 95 this.files = audioFileInput?.files 96 } 97 else { 98 this.canTranscribe = false; 99 this.files = undefined; 100 } 101 }, 102 async doTranscription() { 103 await getTranscriptions(); 104 } 105 } 106 }); 53 methods: 54 { 55 openFilePicker() { 56 document.getElementById("audioFileInput")?.click(); 57 }, 58 onFilesChanged() 59 { 60 /** @type {HTMLInputElement} */ 61 // @ts-ignore | Object could be null 62 const audioFileInput = document.getElementById("audioFileInput"); 63 64 if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) { 65 this.canTranscribe = true; 66 this.files = audioFileInput?.files 67 } 68 else { 69 this.canTranscribe = false; 70 this.files = undefined; 71 } 72 }, 73 async doTranscription() { 74 // TODO: Push files to queue. If currently transcribing, good to go; it'll pull off it. 75 // Else call getTranscriptions(); 76 // Then, we don't have to worry about preventing the user from transcribing multiple items. 77 await getTranscriptions(); 78 } 79 } 80 } 81 ); 107 82 const AudioUploadVM = AudioUploadComponent.mount("#audioUploadContainer"); 83 84 const TranscriptionsListComponent = Vue.createApp( 85 { 86 data() 87 { 88 return { 89 /** @type {TranscriptionModel[]} */ 90 transcriptions: [], 91 /** @type {TranscriptionError[]} */ 92 failures: [] 93 } 94 } 95 } 96 ) 97 const TranscriptionsListVM = TranscriptionsListComponent.mount("#transcriptionsDisplayContainer"); 108 98 109 99 /** … … 133 123 async function getTranscriptions() 134 124 { 125 AudioUploadVM.isTranscribing = true; 126 135 127 /** @type {FileList} */ 136 128 const files = AudioUploadVM.files; 137 129 138 AudioUploadVM.isTranscribing = true; 139 await delay(1000); // TODO: Remove - UI testing purposes only 130 await delay(500); // TODO: Remove - UI testing purposes only 140 131 // Cache the file list so that we can playback audio in the future 141 132 for (const file of files) { … … 151 142 { 152 143 if (!t.success) { 153 insertError(t.file_name, t.log);144 TranscriptionsListVM.failures.push(new TranscriptionError(t.log, t.file_name)); 154 145 } 155 else { 156 insertTranscription(t);146 else { 147 TranscriptionsListVM.transcriptions.push(t); 157 148 } 158 149 } … … 163 154 console.error("Failed to transcribe files"); 164 155 console.error(e); 165 insertError("all", e.statusMessage);156 TranscriptionsListVM.failures.push(e); 166 157 } 167 158 168 159 AudioUploadVM.isTranscribing = false; 169 }170 171 /**172 * Removes any transcriptions from the UI list.173 */174 function clearTranscriptions()175 {176 cachedAudioFileList = new Map();177 while (TRANSCRIPTIONS_LIST.lastChild) {178 TRANSCRIPTIONS_LIST.removeChild(TRANSCRIPTIONS_LIST.lastChild);179 }180 160 } 181 161 … … 324 304 } 325 305 } 326 327 /**328 * Inserts a transcription error into the DOM.329 *330 * @param {String} fileName The file for which an error occured.331 * @param {String | null} statusMessage An informative error message to display.332 */333 function insertError(fileName, statusMessage)334 {335 /** @type {HTMLLIElement} */336 // @ts-ignore337 const clone = ERROR_TEMPLATE.content.firstElementChild.cloneNode(true);338 339 /** @type {NodeListOf<HTMLSpanElement>} */340 const spans = clone.querySelectorAll("span");341 spans[0].textContent = statusMessage;342 spans[1].textContent = fileName;343 344 TRANSCRIPTIONS_LIST.appendChild(clone);345 } -
main/trunk/model-interfaces-dev/atea/style/asr.scss
r35282 r35284 92 92 margin: 0 1px 3px 1px; /* Keeps space around the box shadow */ 93 93 94 -webkit-transition-duration: 0. 2s;95 transition-duration: 0. 2s;94 -webkit-transition-duration: 0.15s; 95 transition-duration: 0.15s; 96 96 97 97 span { … … 144 144 145 145 /* === End theme definitions === */ 146 147 /* === Start theme application === */ 148 149 button { 150 @extend .btn-primary; 151 } 152 153 /* === End theme application === */ 146 154 147 155 body { … … 172 180 } 173 181 174 #btnBeginTranscription {175 float: right;176 }177 182 178 183 /* Transcriptions */ … … 184 189 185 190 .transcription__container { 186 margin-bottom: 1em; 191 padding: 0.5em; 192 193 -webkit-transition-duration: 0.15s; 194 transition-duration: 0.15s; 187 195 188 196 &:last-child { 189 197 margin-bottom: 0; 198 } 199 200 &:hover { 201 background-color: #EEE; 190 202 } 191 203 } … … 211 223 } 212 224 213 .error-list-item { 214 list-style-type: none; 215 background-color: rgba(255, 0, 0, 0.329); 216 border: 1px solid rgba(255, 0, 0, 0.651); 217 border-radius: 5px; 218 margin-bottom: 5px; 219 padding: 1em; 220 } 221 222 .error-list-item .spaced-block { 223 margin-bottom: 8px; 224 225 &:last-child { 226 margin-bottom: 0; 227 } 225 .transcription__error-container { 226 @extend .transcription__container; 227 228 background-color: rgba(255, 0, 0, 0.226); 229 230 &:hover { 231 background-color: rgba(255, 0, 0, 0.226); 232 } 228 233 } 229 234 -
main/trunk/model-interfaces-dev/atea/transform/pages/asr.xsl
r35282 r35284 48 48 <link href="interfaces/{$interface_name}/style/asr.css" rel="stylesheet" type="text/css" /> 49 49 50 <span class="monospace-font-size">ngÄ tama a rangi |</span> 50 <section id="audio-transcription-container" class="paper"> 51 <!-- Used to calculate the character size of our monospace font --> 52 <span class="monospace-font-size">ngÄ tama a rangi</span> 51 53 52 <section id="audio-transcription-container" class="paper">53 54 <!-- Contains the file input, transcribe button and transcription progress indicator --> 54 55 <div id="audioUploadContainer"> 55 <button class="btn-primary" type="button" v-on:click="openFilePicker"> 56 <button type="button" v-on:click="openFilePicker" 57 v-bind:disabled="isTranscribing"> 56 58 <span class="material-icons"></span> <!-- file_upload --> 57 59 <span>Upload Audio Files</span> … … 61 63 accept="audio/wav" multiple="multiple" v-bind:disabled="isTranscribing" /> 62 64 63 <button id="btnBeginTranscription" class="btn-primary" type="submit"65 <button style="float: right;" type="submit" 64 66 v-bind:disabled="!canTranscribe" v-on:click="doTranscription"> 65 67 <span class="material-icons"></span> <!-- history_edu --> … … 70 72 </div> 71 73 72 <!-- <div class="audio-slider__container">73 <button type="button" class="btn-fab">74 <span class="material-icons mdi-xl"></span>75 </button>76 <input type="range" min="0" value="0" class="audio-slider__range" />77 <span class="audio-slider__value">1:23</span>78 </div> -->79 80 74 <!-- Contains the audio element and transcription list --> 81 75 <div id="transcriptionsDisplayContainer"> … … 84 78 <source id="transcriptionAudioSource" /> 85 79 </audio> 80 81 <ul class="transcription__list"> 82 <li v-for="transcription in transcriptions" class="transcription__container"> 83 {{ transcription.transcription }} 84 </li> 85 <li v-for="failure in failures" class="transcription__error-container"> 86 Failed to transcribe <i v-if="failure.file">{{ failure.file }}</i><br/> 87 <span v-if="failure.message">Reason: {{ failure.message }}</span> 88 </li> 89 </ul> 90 91 <ul class="transcription__list"> 92 93 </ul> 86 94 87 < ul id="transcriptionsList" class="transcription__list"></ul>95 <!-- <ul id="transcriptionsList" class="transcription__list"></ul> --> 88 96 89 97 <template id="transcriptionTemplate"> … … 117 125 </li> 118 126 </template> 119 120 <template id="errorTemplate">121 <li class="error-list-item">122 <div>123 <div class="spaced-block">124 <b>Transcription Failed: </b><span></span>125 </div>126 <div class="spaced-block">127 <b>File: </b><span></span>128 </div>129 </div>130 </li>131 </template>132 127 </div> 128 129 <!-- <div class="audio-slider__container"> 130 <button type="button" class="btn-fab"> 131 <span class="material-icons mdi-xl"></span> 132 </button> 133 <input type="range" min="0" value="0" class="audio-slider__range" /> 134 <span class="audio-slider__value">1:23</span> 135 </div> --> 133 136 </section> 134 137 135 138 <!-- TODO: Switch to production version for release builds --> 136 139 <gsf:script src="https://unpkg.com/vue@next"></gsf:script> 137 <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js"></gsf:script> 140 <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js" type="module"></gsf:script> 141 <!-- <script type="module" src="interfaces/{$interface_name}/js/asr/asr-controller.js"> 142 <xsl:comment>Filler</xsl:comment>> 143 </script> --> 138 144 </xsl:template> 139 145
Note:
See TracChangeset
for help on using the changeset viewer.