source: main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioUpload.vue@ 35432

Last change on this file since 35432 was 35432, checked in by cstephen, 3 years ago

Prevent duplicate transcriptions being created

File size: 6.9 KB
Line 
1<template>
2 <!-- Contains the file input, transcribe button and transcription progress indicator -->
3 <div class="audio-file-picker">
4 <div class="input-bar">
5 <div class="text-input-sl" style="cursor: pointer;" @click="openFilePicker">
6 <span class="text-placeholder material-icons" v-if="!anyFiles">&#xe226;</span> <!-- attach_file -->
7 <span class="text-placeholder" v-if="!anyFiles">{{ translations.get("AudioUpload_SelectFileText") }}</span>
8 <span v-if="anyFiles">{{ getFileNameList }}</span>
9 </div>
10
11 <input ref="audioFileInput" type="file" @input="onFilesChanged"
12 accept="audio/wav" multiple :disabled="isTranscribing" />
13
14 <button class="btn-primary" type="submit" :disabled="!anyFiles || isTranscribing" @click="doTranscription">
15 <span class="material-icons">&#xEA3E;</span> <!-- history_edu -->
16 <span>{{ translations.get("AudioUpload_TranscribeFiles") }}</span>
17 </button>
18 </div>
19
20 <progress v-if="isTranscribing" class="indeterminateLoadingBar" />
21
22 <ul class="list-view" v-if="failures.size > 0">
23 <li v-for="[id, failure] in failures" :key="failure.id" class="list-view__item transcription-error-container">
24 <div>
25 {{ translations.get("AudioUpload_TranscriptionFailed_Message") }} <i v-if="failure.fileName">{{ failure.fileName }}</i><br />
26 <span v-if="failure.message">{{ translations.get("AudioUpload_TranscriptionFailed_Reason") }}: {{ failure.message }}</span>
27 </div>
28
29 <button class="btn-fab theme-flat" @click="dismissFailure(id)">
30 <span class="material-icons">&#xE14C;</span> <!-- clear -->
31 </button>
32 </li>
33 </ul>
34 </div>
35</template>
36
37<!-- Add "scoped" attribute to limit CSS to this component only -->
38<style scoped lang="scss">
39.audio-file-picker {
40 display: flex;
41 gap: 0.5em;
42 flex-direction: column;
43
44 input {
45 display: none;
46 }
47
48 .text-container {
49 cursor: pointer;
50 }
51}
52
53.input-bar {
54 display: grid;
55 align-items: stretch;
56 gap: 0.5em;
57 grid-template-columns: 1fr auto;
58}
59
60.transcription-error-container {
61 display: grid;
62 gap: 0.5em;
63 grid-template-columns: 1fr auto;
64 align-items: center;
65
66 border-left: 3px solid red;
67}
68</style>
69
70<script>
71import { mapState } from "vuex";
72import TranscribeService from "../js/TranscribeModule";
73import Util from "../js/Util";
74import { TranscriptionViewModel } from "../main"
75
76class TranscriptionViewFailure {
77 /**
78 * @param {String} fileName The name of the file for which this failure occured.
79 * @param {String} message The reason for this failure.
80 */
81 constructor(fileName, message) {
82 /** @type {String} The UUID of this failure. */
83 this.id = Util.generateUuid();
84
85 /** @type {String | null} The name of the file for which this failure occured. */
86 this.fileName = fileName;
87
88 /** @type {String} The reason for the failure. */
89 this.message = message;
90 }
91}
92
93const transcribeService = new TranscribeService();
94
95export default {
96 name: "AudioUpload",
97 data() {
98 return {
99 /** @type {File[]} */
100 files: [],
101 /** @type {Map<String, TranscriptionViewFailure>} */
102 failures: new Map(),
103 canTranscribe: false,
104 isTranscribing: false
105 }
106 },
107 computed: mapState({
108 translations: state => state.translations,
109 anyFiles() {
110 return this.files?.length > 0;
111 },
112 getFileNameList() {
113 let fileNameList = "";
114
115 for (const file of this.files) {
116 fileNameList += file.name + ", ";
117 }
118
119 return fileNameList.slice(0, fileNameList.length - 2);
120 }
121 }),
122 emits: [ "newTranscription" ],
123 methods: {
124 openFilePicker() {
125 this.$refs.audioFileInput.click();
126 },
127 onFilesChanged() {
128 this.files = [];
129 const files = this.$refs.audioFileInput.files;
130
131 if (files?.length !== undefined && files?.length > 0) {
132 for (const file of files) {
133 this.files.push(file);
134 }
135 }
136 },
137 async doTranscription() {
138 this.isTranscribing = true;
139
140 await this.getTranscriptions(this.files);
141
142 this.files = []; // Clear the file list, as there is no reason the user would want to transcribe the same file multiple times over
143 this.isTranscribing = false;
144 // TODO: Push files to queue. If currently transcribing, good to go; it'll pull off it.
145 // Else call getTranscriptions();
146 // Then, we don't have to worry about preventing the user from transcribing multiple items.
147 },
148 /**
149 * Dismisses a failure
150 * @param {String} id The UUID of the failure.
151 */
152 dismissFailure(id) {
153 this.failures.delete(id);
154 },
155 /**
156 * Gets the transcription of each submitted audio file.
157 * @param {File[]} files The files to transcribe.
158 */
159 async getTranscriptions(files) {
160 await Util.delay(1000); // TODO: Remove - UI testing purposes only
161
162 try {
163 for await (const batch of transcribeService.batchTranscribeFiles(files)) {
164 for (const t of batch) {
165 if (!t.success) {
166 createFailure(this, t);
167 }
168 else {
169 const f = files.find(f => f.name === t.file_name);
170 if (f === undefined) {
171 createFailure(this, t, this.translations.get("ErrorTryAgain"));
172 }
173
174 const tvm = new TranscriptionViewModel(t, f);
175
176 try {
177 this.$store.commit("transcriptionAdd", tvm);
178 }
179 catch (TranscriptionExistsError) {
180 createFailure(this, t, this.translations.get("AudioUpload_AlreadyTranscribed"));
181 }
182 }
183 }
184 }
185 }
186 catch (e) {
187 console.error("Failed to transcribe files");
188 console.error(e);
189
190 const failure = new TranscriptionViewFailure(e.fileName, e.message);
191 this.failures.set(failure.id, failure);
192 }
193
194 function createFailure(that, t, reason = null) {
195 const failure = new TranscriptionViewFailure(t.file_name, reason ?? t.log);
196 that.failures.set(failure.id, failure);
197 }
198 }
199 }
200}
201</script>
Note: See TracBrowser for help on using the repository browser.