source: main/trunk/model-interfaces-dev/atea/macron-restoration/src/components/AudioUpload.vue@ 35714

Last change on this file since 35714 was 35714, checked in by cstephen, 13 months ago

Add macroniser scaffolding.
Update translations.

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