reneco-advanced-input-module 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/voice-input-module.cjs.entry.js +208 -93
- package/dist/cjs/voice-input-module.cjs.entry.js.map +1 -1
- package/dist/cjs/voice-input-module.cjs.js +1 -1
- package/dist/cjs/voice-input-module.entry.cjs.js.map +1 -1
- package/dist/collection/components/voice-input-module/voice-input-module.js +209 -93
- package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -1
- package/dist/components/voice-input-module.js +209 -93
- package/dist/components/voice-input-module.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/voice-input-module.entry.js +208 -93
- package/dist/esm/voice-input-module.entry.js.map +1 -1
- package/dist/esm/voice-input-module.js +1 -1
- package/dist/types/components/voice-input-module/voice-input-module.d.ts +3 -0
- package/dist/voice-input-module/{p-0e2b9ca0.entry.js → p-920964b2.entry.js} +3 -3
- package/dist/voice-input-module/p-920964b2.entry.js.map +1 -0
- package/dist/voice-input-module/voice-input-module.entry.esm.js.map +1 -1
- package/dist/voice-input-module/voice-input-module.esm.js +1 -1
- package/package.json +1 -1
- package/www/build/{p-0e2b9ca0.entry.js → p-920964b2.entry.js} +3 -3
- package/www/build/p-920964b2.entry.js.map +1 -0
- package/www/build/{p-812b92c7.js → p-cbf8974a.js} +1 -1
- package/www/build/voice-input-module.entry.esm.js.map +1 -1
- package/www/build/voice-input-module.esm.js +1 -1
- package/www/index.html +1 -1
- package/dist/voice-input-module/p-0e2b9ca0.entry.js.map +0 -1
- package/www/build/p-0e2b9ca0.entry.js.map +0 -1
|
@@ -37,6 +37,7 @@ export class VoiceFormRecorder {
|
|
|
37
37
|
this.isRecording = false;
|
|
38
38
|
this.isProcessing = false;
|
|
39
39
|
this.hasError = false;
|
|
40
|
+
this.hasTransientError = false;
|
|
40
41
|
this.transcription = '';
|
|
41
42
|
this.filledData = null;
|
|
42
43
|
this.debugInfo = {};
|
|
@@ -49,7 +50,21 @@ export class VoiceFormRecorder {
|
|
|
49
50
|
if (!input.files || input.files.length === 0)
|
|
50
51
|
return;
|
|
51
52
|
const file = input.files[0];
|
|
52
|
-
this.
|
|
53
|
+
const isEn = this.language === 'en';
|
|
54
|
+
// Validate file type
|
|
55
|
+
const validAudioTypes = /^audio\//;
|
|
56
|
+
const validAudioExtensions = /\.(mp3|wav|webm|m4a|ogg|flac|aac|opus)$/i;
|
|
57
|
+
if (!validAudioTypes.test(file.type) && !validAudioExtensions.test(file.name)) {
|
|
58
|
+
this.hasTransientError = true;
|
|
59
|
+
this.statusMessage = isEn
|
|
60
|
+
? `Invalid file type: "${file.name}". Please select an audio file.`
|
|
61
|
+
: `Type de fichier invalide : "${file.name}". Veuillez sélectionner un fichier audio.`;
|
|
62
|
+
input.value = '';
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Reset the input so the same file can be re-selected after an error
|
|
66
|
+
input.value = '';
|
|
67
|
+
await this.processAudioContent(file);
|
|
53
68
|
};
|
|
54
69
|
this.audioRecorder = new AudioRecorderService();
|
|
55
70
|
}
|
|
@@ -109,6 +124,7 @@ export class VoiceFormRecorder {
|
|
|
109
124
|
config: this.parsedConfig
|
|
110
125
|
});
|
|
111
126
|
this.hasError = false;
|
|
127
|
+
this.hasTransientError = false;
|
|
112
128
|
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.idle) || (this.language == 'en' ? 'Select an input method' : 'Sélectionner une méthode de saisie');
|
|
113
129
|
if (this.parsedInputTypes.length === 0) {
|
|
114
130
|
this.inputTypes = 'voice';
|
|
@@ -129,6 +145,91 @@ export class VoiceFormRecorder {
|
|
|
129
145
|
} });
|
|
130
146
|
}
|
|
131
147
|
}
|
|
148
|
+
getMicErrorMessage(error) {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
const isEn = this.language === 'en';
|
|
151
|
+
const errorName = error.name || '';
|
|
152
|
+
if (errorName === 'NotAllowedError' || errorName === 'PermissionDeniedError') {
|
|
153
|
+
return isEn
|
|
154
|
+
? 'Microphone access denied. Allow microphone access in your browser settings and try again.'
|
|
155
|
+
: "Accès au microphone refusé. Autorisez l'accès au microphone dans les paramètres du navigateur et réessayez.";
|
|
156
|
+
}
|
|
157
|
+
if (errorName === 'NotFoundError' || errorName === 'DevicesNotFoundError') {
|
|
158
|
+
return isEn
|
|
159
|
+
? 'No microphone found. Please connect a microphone and try again.'
|
|
160
|
+
: 'Aucun microphone trouvé. Connectez un microphone et réessayez.';
|
|
161
|
+
}
|
|
162
|
+
if (errorName === 'NotReadableError' || errorName === 'TrackStartError') {
|
|
163
|
+
return isEn
|
|
164
|
+
? 'Microphone is busy or unavailable. Close other apps using the microphone and try again.'
|
|
165
|
+
: 'Le microphone est occupé ou indisponible. Fermez les autres applications utilisant le microphone et réessayez.';
|
|
166
|
+
}
|
|
167
|
+
if (errorName === 'AbortError') {
|
|
168
|
+
return isEn
|
|
169
|
+
? 'Microphone access was interrupted. Please try again.'
|
|
170
|
+
: "L'accès au microphone a été interrompu. Veuillez réessayer.";
|
|
171
|
+
}
|
|
172
|
+
if (errorName === 'OverconstrainedError') {
|
|
173
|
+
return isEn
|
|
174
|
+
? 'Microphone does not support the required audio settings.'
|
|
175
|
+
: 'Le microphone ne supporte pas les paramètres audio requis.';
|
|
176
|
+
}
|
|
177
|
+
if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('HTTPS')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('not supported'))) {
|
|
178
|
+
return isEn
|
|
179
|
+
? 'Microphone access requires HTTPS or localhost.'
|
|
180
|
+
: "L'accès au microphone nécessite HTTPS ou localhost.";
|
|
181
|
+
}
|
|
182
|
+
return isEn ? `Microphone error: ${error.message}` : `Erreur microphone : ${error.message}`;
|
|
183
|
+
}
|
|
184
|
+
getReadableErrorMessage(error, phase) {
|
|
185
|
+
const isEn = this.language === 'en';
|
|
186
|
+
const msg = error.message || '';
|
|
187
|
+
if (/401|unauthorized|invalid.{0,20}(api )?key|authentication failed/i.test(msg)) {
|
|
188
|
+
return isEn
|
|
189
|
+
? 'Authentication failed: invalid API key. Check your configuration.'
|
|
190
|
+
: "Échec d'authentification : clé API invalide. Vérifiez votre configuration.";
|
|
191
|
+
}
|
|
192
|
+
if (/403|forbidden/i.test(msg)) {
|
|
193
|
+
return isEn
|
|
194
|
+
? 'Access denied: check your API permissions.'
|
|
195
|
+
: 'Accès refusé : vérifiez vos permissions API.';
|
|
196
|
+
}
|
|
197
|
+
if (/429|rate.{0,10}limit|too many requests/i.test(msg)) {
|
|
198
|
+
return isEn
|
|
199
|
+
? 'Rate limit exceeded. Please wait a moment and try again.'
|
|
200
|
+
: 'Limite de requêtes dépassée. Veuillez attendre quelques secondes et réessayer.';
|
|
201
|
+
}
|
|
202
|
+
if (/404|not found/i.test(msg)) {
|
|
203
|
+
return isEn
|
|
204
|
+
? 'API endpoint not found. Check your proxy URL configuration.'
|
|
205
|
+
: "Endpoint API introuvable. Vérifiez l'URL de votre proxy.";
|
|
206
|
+
}
|
|
207
|
+
if (/5\d\d|server error|internal server|bad gateway|service unavailable/i.test(msg)) {
|
|
208
|
+
return isEn
|
|
209
|
+
? 'Server error. Please try again in a moment.'
|
|
210
|
+
: 'Erreur serveur. Veuillez réessayer dans un instant.';
|
|
211
|
+
}
|
|
212
|
+
if (/network|failed to fetch|err_connection|connection refused/i.test(msg)) {
|
|
213
|
+
return isEn
|
|
214
|
+
? 'Network error: check your connection and proxy server.'
|
|
215
|
+
: 'Erreur réseau : vérifiez votre connexion et le serveur proxy.';
|
|
216
|
+
}
|
|
217
|
+
if (/json|parse error|unexpected token|invalid response/i.test(msg)) {
|
|
218
|
+
return isEn
|
|
219
|
+
? 'Unexpected response from AI service. Please try again.'
|
|
220
|
+
: 'Réponse inattendue du service IA. Veuillez réessayer.';
|
|
221
|
+
}
|
|
222
|
+
if (phase === 'transcription') {
|
|
223
|
+
return isEn ? `Transcription failed: ${msg}` : `Échec de la transcription : ${msg}`;
|
|
224
|
+
}
|
|
225
|
+
if (phase === 'llm') {
|
|
226
|
+
return isEn ? `Form filling failed: ${msg}` : `Échec du remplissage du formulaire : ${msg}`;
|
|
227
|
+
}
|
|
228
|
+
if (phase === 'ocr') {
|
|
229
|
+
return isEn ? `OCR processing failed: ${msg}` : `Échec du traitement OCR : ${msg}`;
|
|
230
|
+
}
|
|
231
|
+
return isEn ? `Processing error: ${msg}` : `Erreur de traitement : ${msg}`;
|
|
232
|
+
}
|
|
132
233
|
async handleRecordClick() {
|
|
133
234
|
if (this.isProcessing)
|
|
134
235
|
return;
|
|
@@ -142,7 +243,7 @@ export class VoiceFormRecorder {
|
|
|
142
243
|
async startRecording() {
|
|
143
244
|
var _a;
|
|
144
245
|
try {
|
|
145
|
-
this.
|
|
246
|
+
this.hasTransientError = false;
|
|
146
247
|
this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
|
|
147
248
|
this.updateDebugInfo('Start Recording Attempt', {});
|
|
148
249
|
await this.audioRecorder.startRecording();
|
|
@@ -155,8 +256,8 @@ export class VoiceFormRecorder {
|
|
|
155
256
|
});
|
|
156
257
|
}
|
|
157
258
|
catch (error) {
|
|
158
|
-
this.
|
|
159
|
-
this.statusMessage =
|
|
259
|
+
this.hasTransientError = true;
|
|
260
|
+
this.statusMessage = this.getMicErrorMessage(error);
|
|
160
261
|
this.updateDebugInfo('Recording Error', { error: error.message });
|
|
161
262
|
this.formFilled.emit({
|
|
162
263
|
success: false,
|
|
@@ -166,40 +267,36 @@ export class VoiceFormRecorder {
|
|
|
166
267
|
}
|
|
167
268
|
async processJsonForm(ocrData) {
|
|
168
269
|
var _a, _b, _c;
|
|
270
|
+
this.isProcessing = true;
|
|
271
|
+
this.hasTransientError = false;
|
|
272
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing document...' : 'Traitement du document ...');
|
|
169
273
|
try {
|
|
170
|
-
this.isProcessing = true;
|
|
171
|
-
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing json...' : `Traitement du json ...`);
|
|
172
|
-
// Extract content from OCR format
|
|
173
274
|
const extractedData = (ocrData === null || ocrData === void 0 ? void 0 : ocrData.content) || ocrData;
|
|
174
275
|
const jsonForm = JSON.stringify(extractedData);
|
|
175
|
-
|
|
276
|
+
if (!extractedData) {
|
|
277
|
+
throw Object.assign(new Error('OCR returned no data'), { phase: 'ocr' });
|
|
278
|
+
}
|
|
176
279
|
this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
|
|
177
280
|
const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
|
|
178
|
-
|
|
179
|
-
|
|
281
|
+
let filledSchema;
|
|
282
|
+
try {
|
|
283
|
+
filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
|
|
284
|
+
}
|
|
285
|
+
catch (llmError) {
|
|
286
|
+
throw Object.assign(new Error(llmError.message), { phase: 'llm' });
|
|
287
|
+
}
|
|
180
288
|
this.filledData = this.extractFilledData(filledSchema);
|
|
181
|
-
this.updateDebugInfo('Form Filled', {
|
|
182
|
-
filledSchema,
|
|
183
|
-
extractedData: this.filledData
|
|
184
|
-
});
|
|
289
|
+
this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
|
|
185
290
|
this.parsedSchema = this.filledData;
|
|
186
|
-
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire
|
|
187
|
-
this.
|
|
188
|
-
// Emit success event
|
|
189
|
-
this.formFilled.emit({
|
|
190
|
-
success: true,
|
|
191
|
-
data: this.filledData,
|
|
192
|
-
jsonForm: jsonForm
|
|
193
|
-
});
|
|
291
|
+
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
|
|
292
|
+
this.formFilled.emit({ success: true, data: this.filledData, jsonForm });
|
|
194
293
|
}
|
|
195
294
|
catch (error) {
|
|
196
|
-
|
|
197
|
-
this.
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
jsonForm: JSON.stringify(ocrData)
|
|
202
|
-
});
|
|
295
|
+
console.error('OCR processing error:', error);
|
|
296
|
+
this.hasTransientError = true;
|
|
297
|
+
this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'llm');
|
|
298
|
+
this.updateDebugInfo('OCR Processing Error', { phase: error.phase, error: error.message });
|
|
299
|
+
this.formFilled.emit({ success: false, error: error.message, jsonForm: JSON.stringify(ocrData) });
|
|
203
300
|
}
|
|
204
301
|
finally {
|
|
205
302
|
this.isProcessing = false;
|
|
@@ -207,83 +304,100 @@ export class VoiceFormRecorder {
|
|
|
207
304
|
}
|
|
208
305
|
async processAudioContent(audioFile) {
|
|
209
306
|
var _a, _b, _c;
|
|
210
|
-
this.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
307
|
+
this.isProcessing = true;
|
|
308
|
+
this.hasTransientError = false;
|
|
309
|
+
this.updateDebugInfo('Audio Captured', { size: audioFile.size, type: audioFile.type });
|
|
214
310
|
try {
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
311
|
+
// Validate audio file before sending to API
|
|
312
|
+
const MAX_AUDIO_SIZE = 25 * 1024 * 1024; // 25MB (API limit)
|
|
313
|
+
if (audioFile.size <= 44) { // WAV header alone is 44 bytes; effectively empty
|
|
314
|
+
this.hasTransientError = true;
|
|
315
|
+
this.statusMessage = this.language == 'en'
|
|
316
|
+
? 'Recording is empty or too short. Please try again.'
|
|
317
|
+
: "L'enregistrement est vide ou trop court. Veuillez réessayer.";
|
|
318
|
+
this.formFilled.emit({ success: false, error: 'Empty or too short audio' });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (audioFile.size > MAX_AUDIO_SIZE) {
|
|
322
|
+
this.hasTransientError = true;
|
|
323
|
+
this.statusMessage = this.language == 'en'
|
|
324
|
+
? `File too large (${Math.round(audioFile.size / 1024 / 1024)}MB). Maximum is 25MB.`
|
|
325
|
+
: `Fichier trop volumineux (${Math.round(audioFile.size / 1024 / 1024)}Mo). Maximum : 25Mo.`;
|
|
326
|
+
this.formFilled.emit({ success: false, error: 'Audio file too large' });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// Step 1: Transcription
|
|
330
|
+
let transcription;
|
|
331
|
+
try {
|
|
332
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
|
|
333
|
+
transcription = await this.speechToTextService.transcribe(audioFile, this.language);
|
|
334
|
+
this.transcription = transcription;
|
|
335
|
+
this.updateDebugInfo('Transcription Complete', { transcription });
|
|
336
|
+
}
|
|
337
|
+
catch (transcriptionError) {
|
|
338
|
+
throw Object.assign(new Error(transcriptionError.message), { phase: 'transcription' });
|
|
339
|
+
}
|
|
220
340
|
if (!transcription.trim()) {
|
|
221
|
-
|
|
341
|
+
this.hasTransientError = true;
|
|
342
|
+
this.statusMessage = this.language == 'en'
|
|
343
|
+
? 'No speech detected. Please speak clearly and try again.'
|
|
344
|
+
: 'Aucune parole détectée. Parlez clairement et réessayez.';
|
|
345
|
+
this.formFilled.emit({ success: false, error: 'No speech detected', transcription: '' });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// Step 2: LLM form filling
|
|
349
|
+
let filledSchema;
|
|
350
|
+
try {
|
|
351
|
+
this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
|
|
352
|
+
const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
|
|
353
|
+
filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
|
|
354
|
+
}
|
|
355
|
+
catch (llmError) {
|
|
356
|
+
throw Object.assign(new Error(llmError.message), { phase: 'llm' });
|
|
222
357
|
}
|
|
223
|
-
// Fill form using LLM
|
|
224
|
-
this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
|
|
225
|
-
const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
|
|
226
|
-
const filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
|
|
227
|
-
// Extract filled data
|
|
228
358
|
this.filledData = this.extractFilledData(filledSchema);
|
|
229
|
-
this.updateDebugInfo('Form Filled', {
|
|
230
|
-
filledSchema,
|
|
231
|
-
extractedData: this.filledData
|
|
232
|
-
});
|
|
359
|
+
this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
|
|
233
360
|
this.parsedSchema = this.filledData;
|
|
234
|
-
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire
|
|
235
|
-
this.
|
|
236
|
-
// Emit success event
|
|
237
|
-
this.formFilled.emit({
|
|
238
|
-
success: true,
|
|
239
|
-
data: this.filledData,
|
|
240
|
-
transcription: transcription
|
|
241
|
-
});
|
|
361
|
+
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
|
|
362
|
+
this.formFilled.emit({ success: true, data: this.filledData, transcription });
|
|
242
363
|
}
|
|
243
364
|
catch (error) {
|
|
244
|
-
console.error('
|
|
245
|
-
this.
|
|
246
|
-
this.statusMessage =
|
|
247
|
-
this.updateDebugInfo('
|
|
248
|
-
this.formFilled.emit({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
365
|
+
console.error('Audio processing error:', error);
|
|
366
|
+
this.hasTransientError = true;
|
|
367
|
+
this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'audio-upload');
|
|
368
|
+
this.updateDebugInfo('Audio Processing Error', { phase: error.phase, error: error.message });
|
|
369
|
+
this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
this.isProcessing = false;
|
|
253
373
|
}
|
|
254
374
|
}
|
|
255
375
|
async stopRecordingAndProcess() {
|
|
256
376
|
var _a;
|
|
377
|
+
this.isRecording = false;
|
|
378
|
+
this.isProcessing = true;
|
|
379
|
+
this.hasTransientError = false;
|
|
380
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
|
|
381
|
+
this.updateDebugInfo('Stop Recording', {});
|
|
382
|
+
this.recordingStateChanged.emit({ isRecording: false, state: 'processing' });
|
|
257
383
|
try {
|
|
258
|
-
this.isRecording = false;
|
|
259
|
-
this.isProcessing = true;
|
|
260
|
-
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
|
|
261
|
-
this.updateDebugInfo('Stop Recording', {});
|
|
262
|
-
this.recordingStateChanged.emit({
|
|
263
|
-
isRecording: false,
|
|
264
|
-
state: 'processing'
|
|
265
|
-
});
|
|
266
|
-
// Stop recording and get audio blob
|
|
267
384
|
const audioBlob = await this.audioRecorder.stopRecording();
|
|
268
385
|
const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
|
|
269
|
-
|
|
386
|
+
// processAudioContent manages isProcessing itself; await ensures we don't return early
|
|
387
|
+
await this.processAudioContent(audioContent);
|
|
270
388
|
}
|
|
271
389
|
catch (error) {
|
|
272
|
-
|
|
273
|
-
this.
|
|
274
|
-
this.
|
|
275
|
-
this.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
390
|
+
// Only reached if audioRecorder.stopRecording() itself throws
|
|
391
|
+
this.hasTransientError = true;
|
|
392
|
+
this.isProcessing = false;
|
|
393
|
+
this.statusMessage = (this.language == 'en'
|
|
394
|
+
? `Failed to stop recording: ${error.message}`
|
|
395
|
+
: `Impossible d'arrêter l'enregistrement : ${error.message}`);
|
|
396
|
+
this.updateDebugInfo('Stop Recording Error', { error: error.message });
|
|
397
|
+
this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
|
|
280
398
|
}
|
|
281
399
|
finally {
|
|
282
|
-
this.
|
|
283
|
-
this.recordingStateChanged.emit({
|
|
284
|
-
isRecording: false,
|
|
285
|
-
state: 'idle'
|
|
286
|
-
});
|
|
400
|
+
this.recordingStateChanged.emit({ isRecording: false, state: 'idle' });
|
|
287
401
|
}
|
|
288
402
|
}
|
|
289
403
|
extractFilledData(filledData) {
|
|
@@ -593,12 +707,14 @@ export class VoiceFormRecorder {
|
|
|
593
707
|
return (h("button", { class: buttonClass, style: buttonStyle, onClick: () => this.handleRecordClick(), disabled: isDisabled, "aria-label": this.isRecording ? 'Stop recording' : 'Start recording' }, this.isProcessing ? (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("circle", { cx: "12", cy: "12", r: "3" }, h("animate", { attributeName: "r", values: "3;6;3", dur: "1s", repeatCount: "indefinite" }), h("animate", { attributeName: "opacity", values: "1;0.3;1", dur: "1s", repeatCount: "indefinite" })))) : this.isRecording ? (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }))) : (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("circle", { cx: "12", cy: "12", r: "8" })))));
|
|
594
708
|
}
|
|
595
709
|
renderStatusMessage() {
|
|
710
|
+
const hasAnyError = this.hasError || this.hasTransientError;
|
|
596
711
|
const statusClass = [
|
|
597
712
|
'status-text',
|
|
598
|
-
|
|
599
|
-
this.filledData && !
|
|
713
|
+
hasAnyError && 'error',
|
|
714
|
+
this.filledData && !hasAnyError && 'success'
|
|
600
715
|
].filter(Boolean).join(' ');
|
|
601
|
-
|
|
716
|
+
const statusStyle = this.getStatusStyle();
|
|
717
|
+
return h("div", { class: statusClass, style: statusStyle }, this.statusMessage);
|
|
602
718
|
}
|
|
603
719
|
renderFormPreview() {
|
|
604
720
|
if (!this.parsedSchema)
|
|
@@ -771,8 +887,7 @@ export class VoiceFormRecorder {
|
|
|
771
887
|
}
|
|
772
888
|
render() {
|
|
773
889
|
const containerStyle = this.getContainerStyle();
|
|
774
|
-
|
|
775
|
-
return (h("div", { key: '8378e98f02e5b7482929d255c5ec12ff8c2731e4' }, h("div", { key: 'b8a0d873bd4e1b4c8936747c0919ac8c4c15301b', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h("div", { key: '86e7783e3686db378ee16aa91a640f33c3255923', class: "row-audio-area" }, this.renderRecordButton(), this.renderUploadRecordButton(), this.renderUploadButton()), this.displayStatus ? h("div", { class: "status-text", style: statusStyle }, this.statusMessage) : "", this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
|
|
890
|
+
return (h("div", { key: 'd2eb6b74840a57809920b3dbba3fd7dfbeef8531' }, h("div", { key: 'e66e1d4fc23176f9024ca20413535440a8be3ce8', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h("div", { key: '57dd7b882771d1f0bd56a97bfd0a06ffee09428e', class: "row-audio-area" }, this.renderRecordButton(), this.renderUploadRecordButton(), this.renderUploadButton()), this.displayStatus ? this.renderStatusMessage() : "", this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
|
|
776
891
|
}
|
|
777
892
|
static get is() { return "voice-input-module"; }
|
|
778
893
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1127,6 +1242,7 @@ export class VoiceFormRecorder {
|
|
|
1127
1242
|
"isProcessing": {},
|
|
1128
1243
|
"statusMessage": {},
|
|
1129
1244
|
"hasError": {},
|
|
1245
|
+
"hasTransientError": {},
|
|
1130
1246
|
"transcription": {},
|
|
1131
1247
|
"filledData": {},
|
|
1132
1248
|
"debugInfo": {},
|