reneco-advanced-input-module 0.0.22 → 0.0.24
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 +237 -96
- 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.css +34 -0
- package/dist/collection/components/voice-input-module/voice-input-module.js +239 -96
- package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -1
- package/dist/components/voice-input-module.js +239 -96
- 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 +237 -96
- 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 +6 -0
- package/dist/voice-input-module/{p-0e2b9ca0.entry.js → p-b3bf4feb.entry.js} +3 -3
- package/dist/voice-input-module/p-b3bf4feb.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-812b92c7.js → p-3aeb495d.js} +1 -1
- package/www/build/{p-0e2b9ca0.entry.js → p-b3bf4feb.entry.js} +3 -3
- package/www/build/p-b3bf4feb.entry.js.map +1 -0
- 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
|
@@ -341,6 +341,40 @@ input[type="date"].form-input {
|
|
|
341
341
|
user-select: none;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
/* Skeleton loading state */
|
|
345
|
+
@keyframes skeleton-shimmer {
|
|
346
|
+
0% { background-position: -200% 0; }
|
|
347
|
+
100% { background-position: 200% 0; }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.skeleton-circle {
|
|
351
|
+
border-radius: 50%;
|
|
352
|
+
flex-shrink: 0;
|
|
353
|
+
background: linear-gradient(
|
|
354
|
+
90deg,
|
|
355
|
+
#e2e8f0 25%,
|
|
356
|
+
#f8fafc 50%,
|
|
357
|
+
#e2e8f0 75%
|
|
358
|
+
);
|
|
359
|
+
background-size: 200% 100%;
|
|
360
|
+
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.skeleton-status {
|
|
364
|
+
height: 14px;
|
|
365
|
+
border-radius: 7px;
|
|
366
|
+
width: 140px;
|
|
367
|
+
background: linear-gradient(
|
|
368
|
+
90deg,
|
|
369
|
+
#e2e8f0 25%,
|
|
370
|
+
#f8fafc 50%,
|
|
371
|
+
#e2e8f0 75%
|
|
372
|
+
);
|
|
373
|
+
background-size: 200% 100%;
|
|
374
|
+
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
|
375
|
+
animation-delay: 0.15s;
|
|
376
|
+
}
|
|
377
|
+
|
|
344
378
|
/* Force OCR component to respect size */
|
|
345
379
|
ocr-file-uploader {
|
|
346
380
|
display: block;
|
|
@@ -37,10 +37,12 @@ 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 = {};
|
|
43
44
|
this.isReadonlyMode = true; // Start in readonly preview mode
|
|
45
|
+
this.isInitializing = true;
|
|
44
46
|
this.triggerAudioRecordUpload = () => {
|
|
45
47
|
this.fileInputAudioRecord.click();
|
|
46
48
|
};
|
|
@@ -48,14 +50,35 @@ export class VoiceFormRecorder {
|
|
|
48
50
|
const input = event.target;
|
|
49
51
|
if (!input.files || input.files.length === 0)
|
|
50
52
|
return;
|
|
53
|
+
if (this.isProcessing || this.isRecording) {
|
|
54
|
+
input.value = '';
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
51
57
|
const file = input.files[0];
|
|
52
|
-
this.
|
|
58
|
+
const isEn = this.language === 'en';
|
|
59
|
+
// Validate file type
|
|
60
|
+
const validAudioTypes = /^audio\//;
|
|
61
|
+
const validAudioExtensions = /\.(mp3|wav|webm|m4a|ogg|flac|aac|opus)$/i;
|
|
62
|
+
if (!validAudioTypes.test(file.type) && !validAudioExtensions.test(file.name)) {
|
|
63
|
+
this.hasTransientError = true;
|
|
64
|
+
this.statusMessage = isEn
|
|
65
|
+
? `Invalid file type: "${file.name}". Please select an audio file.`
|
|
66
|
+
: `Type de fichier invalide : "${file.name}". Veuillez sélectionner un fichier audio.`;
|
|
67
|
+
input.value = '';
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Reset the input so the same file can be re-selected after an error
|
|
71
|
+
input.value = '';
|
|
72
|
+
await this.processAudioContent(file);
|
|
53
73
|
};
|
|
54
74
|
this.audioRecorder = new AudioRecorderService();
|
|
55
75
|
}
|
|
56
76
|
componentWillLoad() {
|
|
57
77
|
this.initializeServices();
|
|
58
78
|
}
|
|
79
|
+
componentDidLoad() {
|
|
80
|
+
this.isInitializing = false;
|
|
81
|
+
}
|
|
59
82
|
initializeServices() {
|
|
60
83
|
var _a;
|
|
61
84
|
// Parse theme
|
|
@@ -109,6 +132,7 @@ export class VoiceFormRecorder {
|
|
|
109
132
|
config: this.parsedConfig
|
|
110
133
|
});
|
|
111
134
|
this.hasError = false;
|
|
135
|
+
this.hasTransientError = false;
|
|
112
136
|
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
137
|
if (this.parsedInputTypes.length === 0) {
|
|
114
138
|
this.inputTypes = 'voice';
|
|
@@ -129,6 +153,91 @@ export class VoiceFormRecorder {
|
|
|
129
153
|
} });
|
|
130
154
|
}
|
|
131
155
|
}
|
|
156
|
+
getMicErrorMessage(error) {
|
|
157
|
+
var _a, _b;
|
|
158
|
+
const isEn = this.language === 'en';
|
|
159
|
+
const errorName = error.name || '';
|
|
160
|
+
if (errorName === 'NotAllowedError' || errorName === 'PermissionDeniedError') {
|
|
161
|
+
return isEn
|
|
162
|
+
? 'Microphone access denied. Allow microphone access in your browser settings and try again.'
|
|
163
|
+
: "Accès au microphone refusé. Autorisez l'accès au microphone dans les paramètres du navigateur et réessayez.";
|
|
164
|
+
}
|
|
165
|
+
if (errorName === 'NotFoundError' || errorName === 'DevicesNotFoundError') {
|
|
166
|
+
return isEn
|
|
167
|
+
? 'No microphone found. Please connect a microphone and try again.'
|
|
168
|
+
: 'Aucun microphone trouvé. Connectez un microphone et réessayez.';
|
|
169
|
+
}
|
|
170
|
+
if (errorName === 'NotReadableError' || errorName === 'TrackStartError') {
|
|
171
|
+
return isEn
|
|
172
|
+
? 'Microphone is busy or unavailable. Close other apps using the microphone and try again.'
|
|
173
|
+
: 'Le microphone est occupé ou indisponible. Fermez les autres applications utilisant le microphone et réessayez.';
|
|
174
|
+
}
|
|
175
|
+
if (errorName === 'AbortError') {
|
|
176
|
+
return isEn
|
|
177
|
+
? 'Microphone access was interrupted. Please try again.'
|
|
178
|
+
: "L'accès au microphone a été interrompu. Veuillez réessayer.";
|
|
179
|
+
}
|
|
180
|
+
if (errorName === 'OverconstrainedError') {
|
|
181
|
+
return isEn
|
|
182
|
+
? 'Microphone does not support the required audio settings.'
|
|
183
|
+
: 'Le microphone ne supporte pas les paramètres audio requis.';
|
|
184
|
+
}
|
|
185
|
+
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'))) {
|
|
186
|
+
return isEn
|
|
187
|
+
? 'Microphone access requires HTTPS or localhost.'
|
|
188
|
+
: "L'accès au microphone nécessite HTTPS ou localhost.";
|
|
189
|
+
}
|
|
190
|
+
return isEn ? `Microphone error: ${error.message}` : `Erreur microphone : ${error.message}`;
|
|
191
|
+
}
|
|
192
|
+
getReadableErrorMessage(error, phase) {
|
|
193
|
+
const isEn = this.language === 'en';
|
|
194
|
+
const msg = error.message || '';
|
|
195
|
+
if (/401|unauthorized|invalid.{0,20}(api )?key|authentication failed/i.test(msg)) {
|
|
196
|
+
return isEn
|
|
197
|
+
? 'Authentication failed: invalid API key. Check your configuration.'
|
|
198
|
+
: "Échec d'authentification : clé API invalide. Vérifiez votre configuration.";
|
|
199
|
+
}
|
|
200
|
+
if (/403|forbidden/i.test(msg)) {
|
|
201
|
+
return isEn
|
|
202
|
+
? 'Access denied: check your API permissions.'
|
|
203
|
+
: 'Accès refusé : vérifiez vos permissions API.';
|
|
204
|
+
}
|
|
205
|
+
if (/429|rate.{0,10}limit|too many requests/i.test(msg)) {
|
|
206
|
+
return isEn
|
|
207
|
+
? 'Rate limit exceeded. Please wait a moment and try again.'
|
|
208
|
+
: 'Limite de requêtes dépassée. Veuillez attendre quelques secondes et réessayer.';
|
|
209
|
+
}
|
|
210
|
+
if (/404|not found/i.test(msg)) {
|
|
211
|
+
return isEn
|
|
212
|
+
? 'API endpoint not found. Check your proxy URL configuration.'
|
|
213
|
+
: "Endpoint API introuvable. Vérifiez l'URL de votre proxy.";
|
|
214
|
+
}
|
|
215
|
+
if (/5\d\d|server error|internal server|bad gateway|service unavailable/i.test(msg)) {
|
|
216
|
+
return isEn
|
|
217
|
+
? 'Server error. Please try again in a moment.'
|
|
218
|
+
: 'Erreur serveur. Veuillez réessayer dans un instant.';
|
|
219
|
+
}
|
|
220
|
+
if (/network|failed to fetch|err_connection|connection refused/i.test(msg)) {
|
|
221
|
+
return isEn
|
|
222
|
+
? 'Network error: check your connection and proxy server.'
|
|
223
|
+
: 'Erreur réseau : vérifiez votre connexion et le serveur proxy.';
|
|
224
|
+
}
|
|
225
|
+
if (/json|parse error|unexpected token|invalid response/i.test(msg)) {
|
|
226
|
+
return isEn
|
|
227
|
+
? 'Unexpected response from AI service. Please try again.'
|
|
228
|
+
: 'Réponse inattendue du service IA. Veuillez réessayer.';
|
|
229
|
+
}
|
|
230
|
+
if (phase === 'transcription') {
|
|
231
|
+
return isEn ? `Transcription failed: ${msg}` : `Échec de la transcription : ${msg}`;
|
|
232
|
+
}
|
|
233
|
+
if (phase === 'llm') {
|
|
234
|
+
return isEn ? `Form filling failed: ${msg}` : `Échec du remplissage du formulaire : ${msg}`;
|
|
235
|
+
}
|
|
236
|
+
if (phase === 'ocr') {
|
|
237
|
+
return isEn ? `OCR processing failed: ${msg}` : `Échec du traitement OCR : ${msg}`;
|
|
238
|
+
}
|
|
239
|
+
return isEn ? `Processing error: ${msg}` : `Erreur de traitement : ${msg}`;
|
|
240
|
+
}
|
|
132
241
|
async handleRecordClick() {
|
|
133
242
|
if (this.isProcessing)
|
|
134
243
|
return;
|
|
@@ -142,7 +251,7 @@ export class VoiceFormRecorder {
|
|
|
142
251
|
async startRecording() {
|
|
143
252
|
var _a;
|
|
144
253
|
try {
|
|
145
|
-
this.
|
|
254
|
+
this.hasTransientError = false;
|
|
146
255
|
this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
|
|
147
256
|
this.updateDebugInfo('Start Recording Attempt', {});
|
|
148
257
|
await this.audioRecorder.startRecording();
|
|
@@ -155,8 +264,8 @@ export class VoiceFormRecorder {
|
|
|
155
264
|
});
|
|
156
265
|
}
|
|
157
266
|
catch (error) {
|
|
158
|
-
this.
|
|
159
|
-
this.statusMessage =
|
|
267
|
+
this.hasTransientError = true;
|
|
268
|
+
this.statusMessage = this.getMicErrorMessage(error);
|
|
160
269
|
this.updateDebugInfo('Recording Error', { error: error.message });
|
|
161
270
|
this.formFilled.emit({
|
|
162
271
|
success: false,
|
|
@@ -166,40 +275,38 @@ export class VoiceFormRecorder {
|
|
|
166
275
|
}
|
|
167
276
|
async processJsonForm(ocrData) {
|
|
168
277
|
var _a, _b, _c;
|
|
278
|
+
if (this.isProcessing || this.isRecording)
|
|
279
|
+
return;
|
|
280
|
+
this.isProcessing = true;
|
|
281
|
+
this.hasTransientError = false;
|
|
282
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing document...' : 'Traitement du document ...');
|
|
169
283
|
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
284
|
const extractedData = (ocrData === null || ocrData === void 0 ? void 0 : ocrData.content) || ocrData;
|
|
174
285
|
const jsonForm = JSON.stringify(extractedData);
|
|
175
|
-
|
|
286
|
+
if (!extractedData) {
|
|
287
|
+
throw Object.assign(new Error('OCR returned no data'), { phase: 'ocr' });
|
|
288
|
+
}
|
|
176
289
|
this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
|
|
177
290
|
const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
|
|
178
|
-
|
|
179
|
-
|
|
291
|
+
let filledSchema;
|
|
292
|
+
try {
|
|
293
|
+
filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
|
|
294
|
+
}
|
|
295
|
+
catch (llmError) {
|
|
296
|
+
throw Object.assign(new Error(llmError.message), { phase: 'llm' });
|
|
297
|
+
}
|
|
180
298
|
this.filledData = this.extractFilledData(filledSchema);
|
|
181
|
-
this.updateDebugInfo('Form Filled', {
|
|
182
|
-
filledSchema,
|
|
183
|
-
extractedData: this.filledData
|
|
184
|
-
});
|
|
299
|
+
this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
|
|
185
300
|
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
|
-
});
|
|
301
|
+
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
|
|
302
|
+
this.formFilled.emit({ success: true, data: this.filledData, jsonForm });
|
|
194
303
|
}
|
|
195
304
|
catch (error) {
|
|
196
|
-
|
|
197
|
-
this.
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
jsonForm: JSON.stringify(ocrData)
|
|
202
|
-
});
|
|
305
|
+
console.error('OCR processing error:', error);
|
|
306
|
+
this.hasTransientError = true;
|
|
307
|
+
this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'llm');
|
|
308
|
+
this.updateDebugInfo('OCR Processing Error', { phase: error.phase, error: error.message });
|
|
309
|
+
this.formFilled.emit({ success: false, error: error.message, jsonForm: JSON.stringify(ocrData) });
|
|
203
310
|
}
|
|
204
311
|
finally {
|
|
205
312
|
this.isProcessing = false;
|
|
@@ -207,83 +314,102 @@ export class VoiceFormRecorder {
|
|
|
207
314
|
}
|
|
208
315
|
async processAudioContent(audioFile) {
|
|
209
316
|
var _a, _b, _c;
|
|
210
|
-
this.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
317
|
+
if (this.isProcessing || this.isRecording)
|
|
318
|
+
return;
|
|
319
|
+
this.isProcessing = true;
|
|
320
|
+
this.hasTransientError = false;
|
|
321
|
+
this.updateDebugInfo('Audio Captured', { size: audioFile.size, type: audioFile.type });
|
|
214
322
|
try {
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
323
|
+
// Validate audio file before sending to API
|
|
324
|
+
const MAX_AUDIO_SIZE = 25 * 1024 * 1024; // 25MB (API limit)
|
|
325
|
+
if (audioFile.size <= 44) { // WAV header alone is 44 bytes; effectively empty
|
|
326
|
+
this.hasTransientError = true;
|
|
327
|
+
this.statusMessage = this.language == 'en'
|
|
328
|
+
? 'Recording is empty or too short. Please try again.'
|
|
329
|
+
: "L'enregistrement est vide ou trop court. Veuillez réessayer.";
|
|
330
|
+
this.formFilled.emit({ success: false, error: 'Empty or too short audio' });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (audioFile.size > MAX_AUDIO_SIZE) {
|
|
334
|
+
this.hasTransientError = true;
|
|
335
|
+
this.statusMessage = this.language == 'en'
|
|
336
|
+
? `File too large (${Math.round(audioFile.size / 1024 / 1024)}MB). Maximum is 25MB.`
|
|
337
|
+
: `Fichier trop volumineux (${Math.round(audioFile.size / 1024 / 1024)}Mo). Maximum : 25Mo.`;
|
|
338
|
+
this.formFilled.emit({ success: false, error: 'Audio file too large' });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Step 1: Transcription
|
|
342
|
+
let transcription;
|
|
343
|
+
try {
|
|
344
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
|
|
345
|
+
transcription = await this.speechToTextService.transcribe(audioFile, this.language);
|
|
346
|
+
this.transcription = transcription;
|
|
347
|
+
this.updateDebugInfo('Transcription Complete', { transcription });
|
|
348
|
+
}
|
|
349
|
+
catch (transcriptionError) {
|
|
350
|
+
throw Object.assign(new Error(transcriptionError.message), { phase: 'transcription' });
|
|
351
|
+
}
|
|
220
352
|
if (!transcription.trim()) {
|
|
221
|
-
|
|
353
|
+
this.hasTransientError = true;
|
|
354
|
+
this.statusMessage = this.language == 'en'
|
|
355
|
+
? 'No speech detected. Please speak clearly and try again.'
|
|
356
|
+
: 'Aucune parole détectée. Parlez clairement et réessayez.';
|
|
357
|
+
this.formFilled.emit({ success: false, error: 'No speech detected', transcription: '' });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
// Step 2: LLM form filling
|
|
361
|
+
let filledSchema;
|
|
362
|
+
try {
|
|
363
|
+
this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
|
|
364
|
+
const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
|
|
365
|
+
filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
|
|
366
|
+
}
|
|
367
|
+
catch (llmError) {
|
|
368
|
+
throw Object.assign(new Error(llmError.message), { phase: 'llm' });
|
|
222
369
|
}
|
|
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
370
|
this.filledData = this.extractFilledData(filledSchema);
|
|
229
|
-
this.updateDebugInfo('Form Filled', {
|
|
230
|
-
filledSchema,
|
|
231
|
-
extractedData: this.filledData
|
|
232
|
-
});
|
|
371
|
+
this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
|
|
233
372
|
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
|
-
});
|
|
373
|
+
this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
|
|
374
|
+
this.formFilled.emit({ success: true, data: this.filledData, transcription });
|
|
242
375
|
}
|
|
243
376
|
catch (error) {
|
|
244
|
-
console.error('
|
|
245
|
-
this.
|
|
246
|
-
this.statusMessage =
|
|
247
|
-
this.updateDebugInfo('
|
|
248
|
-
this.formFilled.emit({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
377
|
+
console.error('Audio processing error:', error);
|
|
378
|
+
this.hasTransientError = true;
|
|
379
|
+
this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'audio-upload');
|
|
380
|
+
this.updateDebugInfo('Audio Processing Error', { phase: error.phase, error: error.message });
|
|
381
|
+
this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
this.isProcessing = false;
|
|
253
385
|
}
|
|
254
386
|
}
|
|
255
387
|
async stopRecordingAndProcess() {
|
|
256
388
|
var _a;
|
|
389
|
+
this.isRecording = false;
|
|
390
|
+
this.isProcessing = true;
|
|
391
|
+
this.hasTransientError = false;
|
|
392
|
+
this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
|
|
393
|
+
this.updateDebugInfo('Stop Recording', {});
|
|
394
|
+
this.recordingStateChanged.emit({ isRecording: false, state: 'processing' });
|
|
257
395
|
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
396
|
const audioBlob = await this.audioRecorder.stopRecording();
|
|
268
397
|
const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
|
|
269
|
-
|
|
398
|
+
// processAudioContent manages isProcessing itself; await ensures we don't return early
|
|
399
|
+
await this.processAudioContent(audioContent);
|
|
270
400
|
}
|
|
271
401
|
catch (error) {
|
|
272
|
-
|
|
273
|
-
this.
|
|
274
|
-
this.
|
|
275
|
-
this.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
402
|
+
// Only reached if audioRecorder.stopRecording() itself throws
|
|
403
|
+
this.hasTransientError = true;
|
|
404
|
+
this.isProcessing = false;
|
|
405
|
+
this.statusMessage = (this.language == 'en'
|
|
406
|
+
? `Failed to stop recording: ${error.message}`
|
|
407
|
+
: `Impossible d'arrêter l'enregistrement : ${error.message}`);
|
|
408
|
+
this.updateDebugInfo('Stop Recording Error', { error: error.message });
|
|
409
|
+
this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
|
|
280
410
|
}
|
|
281
411
|
finally {
|
|
282
|
-
this.
|
|
283
|
-
this.recordingStateChanged.emit({
|
|
284
|
-
isRecording: false,
|
|
285
|
-
state: 'idle'
|
|
286
|
-
});
|
|
412
|
+
this.recordingStateChanged.emit({ isRecording: false, state: 'idle' });
|
|
287
413
|
}
|
|
288
414
|
}
|
|
289
415
|
extractFilledData(filledData) {
|
|
@@ -508,7 +634,7 @@ export class VoiceFormRecorder {
|
|
|
508
634
|
textColor: ocrStyle.textColor || '#ffffff'
|
|
509
635
|
}
|
|
510
636
|
};
|
|
511
|
-
const isDisabled = this.isProcessing || this.hasError;
|
|
637
|
+
const isDisabled = this.isProcessing || this.hasError || this.isRecording;
|
|
512
638
|
return (h("div", { style: { width: size, height: size, flexShrink: '0', opacity: isDisabled ? '0.6' : '1', pointerEvents: isDisabled ? 'none' : 'auto' } }, h("ocr-file-uploader", { batch: false, "json-schema": this.convertSchemaToOcrFormat(this.parsedSchema), callback: (data) => { this.processJsonForm(data); }, theme: JSON.stringify(ocrTheme), style: { width: '100%', height: '100%' } })));
|
|
513
639
|
}
|
|
514
640
|
convertSchemaToOcrFormat(schema) {
|
|
@@ -563,7 +689,7 @@ export class VoiceFormRecorder {
|
|
|
563
689
|
return;
|
|
564
690
|
const audioStyle = ((_a = this.parsedTheme.buttons) === null || _a === void 0 ? void 0 : _a.audio) || {};
|
|
565
691
|
const size = audioStyle.size || '50px';
|
|
566
|
-
const isDisabled = this.isProcessing || this.hasError;
|
|
692
|
+
const isDisabled = this.isProcessing || this.hasError || this.isRecording;
|
|
567
693
|
const buttonStyle = {
|
|
568
694
|
stroke: audioStyle.backgroundColor || '#3b82f6',
|
|
569
695
|
fill: audioStyle.backgroundColor || '#3b82f6',
|
|
@@ -592,13 +718,26 @@ export class VoiceFormRecorder {
|
|
|
592
718
|
};
|
|
593
719
|
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
720
|
}
|
|
721
|
+
renderLoadingSkeleton() {
|
|
722
|
+
var _a;
|
|
723
|
+
const types = this.parsedInputTypes.length > 0 ? this.parsedInputTypes : ['voice'];
|
|
724
|
+
const containerStyle = this.getContainerStyle();
|
|
725
|
+
const buttons = (((_a = this.parsedTheme) === null || _a === void 0 ? void 0 : _a.buttons) || {});
|
|
726
|
+
const getSize = (type) => { var _a, _b; return ((_a = buttons[type]) === null || _a === void 0 ? void 0 : _a.size) || ((_b = buttons.voice) === null || _b === void 0 ? void 0 : _b.size) || '50px'; };
|
|
727
|
+
return (h("div", null, h("div", { class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h("div", { class: "row-audio-area" }, types.map(type => {
|
|
728
|
+
const size = getSize(type);
|
|
729
|
+
return h("div", { class: "skeleton-circle", style: { width: size, height: size } });
|
|
730
|
+
})), this.displayStatus && h("div", { class: "skeleton-status" }))));
|
|
731
|
+
}
|
|
595
732
|
renderStatusMessage() {
|
|
733
|
+
const hasAnyError = this.hasError || this.hasTransientError;
|
|
596
734
|
const statusClass = [
|
|
597
735
|
'status-text',
|
|
598
|
-
|
|
599
|
-
this.filledData && !
|
|
736
|
+
hasAnyError && 'error',
|
|
737
|
+
this.filledData && !hasAnyError && 'success'
|
|
600
738
|
].filter(Boolean).join(' ');
|
|
601
|
-
|
|
739
|
+
const statusStyle = this.getStatusStyle();
|
|
740
|
+
return h("div", { class: statusClass, style: statusStyle }, this.statusMessage);
|
|
602
741
|
}
|
|
603
742
|
renderFormPreview() {
|
|
604
743
|
if (!this.parsedSchema)
|
|
@@ -770,9 +909,11 @@ export class VoiceFormRecorder {
|
|
|
770
909
|
};
|
|
771
910
|
}
|
|
772
911
|
render() {
|
|
912
|
+
if (this.isInitializing) {
|
|
913
|
+
return this.renderLoadingSkeleton();
|
|
914
|
+
}
|
|
773
915
|
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() : "")));
|
|
916
|
+
return (h("div", null, h("div", { class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h("div", { class: "row-audio-area" }, this.renderRecordButton(), this.renderUploadRecordButton(), this.renderUploadButton()), this.displayStatus ? this.renderStatusMessage() : "", this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
|
|
776
917
|
}
|
|
777
918
|
static get is() { return "voice-input-module"; }
|
|
778
919
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1127,10 +1268,12 @@ export class VoiceFormRecorder {
|
|
|
1127
1268
|
"isProcessing": {},
|
|
1128
1269
|
"statusMessage": {},
|
|
1129
1270
|
"hasError": {},
|
|
1271
|
+
"hasTransientError": {},
|
|
1130
1272
|
"transcription": {},
|
|
1131
1273
|
"filledData": {},
|
|
1132
1274
|
"debugInfo": {},
|
|
1133
|
-
"isReadonlyMode": {}
|
|
1275
|
+
"isReadonlyMode": {},
|
|
1276
|
+
"isInitializing": {}
|
|
1134
1277
|
};
|
|
1135
1278
|
}
|
|
1136
1279
|
static get events() {
|