reneco-advanced-input-module 0.0.21 → 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.
Files changed (44) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/voice-input-module.cjs.entry.js +481 -154
  3. package/dist/cjs/voice-input-module.cjs.entry.js.map +1 -1
  4. package/dist/cjs/voice-input-module.cjs.js +1 -1
  5. package/dist/cjs/voice-input-module.entry.cjs.js.map +1 -1
  6. package/dist/collection/components/voice-input-module/voice-input-module.js +295 -96
  7. package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -1
  8. package/dist/collection/services/audio-recorder.service.js +61 -44
  9. package/dist/collection/services/audio-recorder.service.js.map +1 -1
  10. package/dist/collection/services/llm.service.js +137 -8
  11. package/dist/collection/services/llm.service.js.map +1 -1
  12. package/dist/collection/services/speech-to-text.service.js +39 -5
  13. package/dist/collection/services/speech-to-text.service.js.map +1 -1
  14. package/dist/collection/types/service-providers.types.js +9 -1
  15. package/dist/collection/types/service-providers.types.js.map +1 -1
  16. package/dist/components/voice-input-module.js +489 -155
  17. package/dist/components/voice-input-module.js.map +1 -1
  18. package/dist/esm/loader.js +1 -1
  19. package/dist/esm/voice-input-module.entry.js +481 -154
  20. package/dist/esm/voice-input-module.entry.js.map +1 -1
  21. package/dist/esm/voice-input-module.js +1 -1
  22. package/dist/types/components/voice-input-module/voice-input-module.d.ts +6 -0
  23. package/dist/types/components.d.ts +18 -0
  24. package/dist/types/services/audio-recorder.service.d.ts +5 -0
  25. package/dist/types/services/llm.service.d.ts +22 -0
  26. package/dist/types/services/speech-to-text.service.d.ts +8 -0
  27. package/dist/types/types/service-providers.types.d.ts +6 -2
  28. package/dist/voice-input-module/p-920964b2.entry.js +3 -0
  29. package/dist/voice-input-module/p-920964b2.entry.js.map +1 -0
  30. package/dist/voice-input-module/voice-input-module.entry.esm.js.map +1 -1
  31. package/dist/voice-input-module/voice-input-module.esm.js +1 -1
  32. package/package.json +1 -1
  33. package/readme.md +81 -0
  34. package/www/build/p-920964b2.entry.js +3 -0
  35. package/www/build/p-920964b2.entry.js.map +1 -0
  36. package/www/build/p-cbf8974a.js +2 -0
  37. package/www/build/voice-input-module.entry.esm.js.map +1 -1
  38. package/www/build/voice-input-module.esm.js +1 -1
  39. package/www/index.html +12 -1
  40. package/dist/voice-input-module/p-4e449895.entry.js +0 -3
  41. package/dist/voice-input-module/p-4e449895.entry.js.map +0 -1
  42. package/www/build/p-3a11e8d2.js +0 -2
  43. package/www/build/p-4e449895.entry.js +0 -3
  44. package/www/build/p-4e449895.entry.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { h } from "@stencil/core";
2
+ import { TRANSCRIPTION_MODELS, COMPLETION_MODELS } from "../../types/service-providers.types";
2
3
  import { AudioRecorderService } from "../../services/audio-recorder.service";
3
4
  import { SpeechToTextServiceFactory } from "../../services/speech-to-text.service";
4
5
  import { LLMServiceFactory } from "../../services/llm.service";
@@ -23,6 +24,8 @@ export class VoiceFormRecorder {
23
24
  this.apiProxyUrl = 'http://localhost:8492';
24
25
  this.transcriptionModel = 'gpt-4o-transcribe';
25
26
  this.completionModel = 'gpt-5-mini';
27
+ this.transcriptionProvider = 'openai';
28
+ this.completionProvider = 'openai';
26
29
  this.context = undefined;
27
30
  this.classificationRootUrl = 'http://localhost';
28
31
  this.language = 'en';
@@ -34,6 +37,7 @@ export class VoiceFormRecorder {
34
37
  this.isRecording = false;
35
38
  this.isProcessing = false;
36
39
  this.hasError = false;
40
+ this.hasTransientError = false;
37
41
  this.transcription = '';
38
42
  this.filledData = null;
39
43
  this.debugInfo = {};
@@ -46,7 +50,21 @@ export class VoiceFormRecorder {
46
50
  if (!input.files || input.files.length === 0)
47
51
  return;
48
52
  const file = input.files[0];
49
- this.processAudioContent(file);
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);
50
68
  };
51
69
  this.audioRecorder = new AudioRecorderService();
52
70
  }
@@ -70,17 +88,33 @@ export class VoiceFormRecorder {
70
88
  this.updateDebugInfo(errorMessage, { error: errorMessage });
71
89
  }
72
90
  else {
91
+ // Validate transcription provider/model
92
+ const allowedTranscriptionModels = TRANSCRIPTION_MODELS[this.transcriptionProvider];
93
+ if (!allowedTranscriptionModels) {
94
+ throw new Error(`Unsupported transcription provider: '${this.transcriptionProvider}'. Allowed: openai, mistral`);
95
+ }
96
+ if (!allowedTranscriptionModels.includes(this.transcriptionModel)) {
97
+ throw new Error(`Model '${this.transcriptionModel}' is not allowed for transcription provider '${this.transcriptionProvider}'. Allowed: ${allowedTranscriptionModels.join(', ')}`);
98
+ }
99
+ // Validate completion provider/model
100
+ const allowedCompletionModels = COMPLETION_MODELS[this.completionProvider];
101
+ if (!allowedCompletionModels) {
102
+ throw new Error(`Unsupported completion provider: '${this.completionProvider}'. Allowed: openai, anthropic, mistral`);
103
+ }
104
+ if (!allowedCompletionModels.includes(this.completionModel)) {
105
+ throw new Error(`Model '${this.completionModel}' is not allowed for completion provider '${this.completionProvider}'. Allowed: ${allowedCompletionModels.join(', ')}`);
106
+ }
73
107
  // Parse form schema
74
108
  this.parsedSchema = JSON.parse(this.formJson || '{}');
75
109
  // Parse service configuration
76
110
  this.parsedConfig = JSON.parse(this.serviceConfig || '{}');
77
111
  // Add API key to config if provided via prop
78
112
  if (this.apiKey) {
79
- this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { apiKey: this.apiKey, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { apiKey: this.apiKey, model: this.completionModel }) });
113
+ this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { provider: this.transcriptionProvider, apiKey: this.apiKey, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { provider: this.completionProvider, apiKey: this.apiKey, model: this.completionModel }) });
80
114
  }
81
115
  else {
82
116
  // Use proxy API if no API key provided
83
- this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { useProxy: true, proxyUrl: this.apiProxyUrl, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { useProxy: true, proxyUrl: this.apiProxyUrl, model: this.completionModel }) });
117
+ this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { provider: this.transcriptionProvider, useProxy: true, proxyUrl: this.apiProxyUrl, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { provider: this.completionProvider, useProxy: true, proxyUrl: this.apiProxyUrl, model: this.completionModel }) });
84
118
  }
85
119
  // Initialize services
86
120
  this.speechToTextService = SpeechToTextServiceFactory.create(this.parsedConfig);
@@ -90,6 +124,7 @@ export class VoiceFormRecorder {
90
124
  config: this.parsedConfig
91
125
  });
92
126
  this.hasError = false;
127
+ this.hasTransientError = false;
93
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');
94
129
  if (this.parsedInputTypes.length === 0) {
95
130
  this.inputTypes = 'voice';
@@ -110,6 +145,91 @@ export class VoiceFormRecorder {
110
145
  } });
111
146
  }
112
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
+ }
113
233
  async handleRecordClick() {
114
234
  if (this.isProcessing)
115
235
  return;
@@ -123,7 +243,7 @@ export class VoiceFormRecorder {
123
243
  async startRecording() {
124
244
  var _a;
125
245
  try {
126
- this.hasError = false;
246
+ this.hasTransientError = false;
127
247
  this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
128
248
  this.updateDebugInfo('Start Recording Attempt', {});
129
249
  await this.audioRecorder.startRecording();
@@ -136,8 +256,8 @@ export class VoiceFormRecorder {
136
256
  });
137
257
  }
138
258
  catch (error) {
139
- this.hasError = true;
140
- this.statusMessage = (this.language == 'en' ? `Recording failed: ${error.message}` : `Echec de l'enregistrement : ${error.message}`);
259
+ this.hasTransientError = true;
260
+ this.statusMessage = this.getMicErrorMessage(error);
141
261
  this.updateDebugInfo('Recording Error', { error: error.message });
142
262
  this.formFilled.emit({
143
263
  success: false,
@@ -147,40 +267,36 @@ export class VoiceFormRecorder {
147
267
  }
148
268
  async processJsonForm(ocrData) {
149
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 ...');
150
273
  try {
151
- this.isProcessing = true;
152
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing json...' : `Traitement du json ...`);
153
- // Extract content from OCR format
154
274
  const extractedData = (ocrData === null || ocrData === void 0 ? void 0 : ocrData.content) || ocrData;
155
275
  const jsonForm = JSON.stringify(extractedData);
156
- // Fill form using LLM
276
+ if (!extractedData) {
277
+ throw Object.assign(new Error('OCR returned no data'), { phase: 'ocr' });
278
+ }
157
279
  this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
158
280
  const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
159
- const filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
160
- // Extract filled data
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
+ }
161
288
  this.filledData = this.extractFilledData(filledSchema);
162
- this.updateDebugInfo('Form Filled', {
163
- filledSchema,
164
- extractedData: this.filledData
165
- });
289
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
166
290
  this.parsedSchema = this.filledData;
167
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
168
- this.hasError = false;
169
- // Emit success event
170
- this.formFilled.emit({
171
- success: true,
172
- data: this.filledData,
173
- jsonForm: jsonForm
174
- });
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 });
175
293
  }
176
294
  catch (error) {
177
- this.hasError = true;
178
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
179
- this.formFilled.emit({
180
- success: false,
181
- error: error.message,
182
- jsonForm: JSON.stringify(ocrData)
183
- });
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) });
184
300
  }
185
301
  finally {
186
302
  this.isProcessing = false;
@@ -188,83 +304,100 @@ export class VoiceFormRecorder {
188
304
  }
189
305
  async processAudioContent(audioFile) {
190
306
  var _a, _b, _c;
191
- this.updateDebugInfo('Audio Captured', {
192
- size: audioFile.size,
193
- type: audioFile.type
194
- });
307
+ this.isProcessing = true;
308
+ this.hasTransientError = false;
309
+ this.updateDebugInfo('Audio Captured', { size: audioFile.size, type: audioFile.type });
195
310
  try {
196
- // Transcribe audio
197
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
198
- const transcription = await this.speechToTextService.transcribe(audioFile, this.language);
199
- this.transcription = transcription;
200
- this.updateDebugInfo('Transcription Complete', { transcription });
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
+ }
201
340
  if (!transcription.trim()) {
202
- throw new Error('No speech detected in the recording');
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' });
203
357
  }
204
- // Fill form using LLM
205
- this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
206
- const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
207
- const filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
208
- // Extract filled data
209
358
  this.filledData = this.extractFilledData(filledSchema);
210
- this.updateDebugInfo('Form Filled', {
211
- filledSchema,
212
- extractedData: this.filledData
213
- });
359
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
214
360
  this.parsedSchema = this.filledData;
215
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
216
- this.hasError = false;
217
- // Emit success event
218
- this.formFilled.emit({
219
- success: true,
220
- data: this.filledData,
221
- transcription: transcription
222
- });
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 });
223
363
  }
224
364
  catch (error) {
225
- console.error('Transcription error:', error);
226
- this.hasError = true;
227
- this.statusMessage = (this.language == 'en' ? 'Audio processing failed.' : 'Échec du traitement audio.');
228
- this.updateDebugInfo('Transcription Error', { error: error.message });
229
- this.formFilled.emit({
230
- success: false,
231
- error: error.message,
232
- transcription: this.transcription
233
- });
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;
234
373
  }
235
374
  }
236
375
  async stopRecordingAndProcess() {
237
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' });
238
383
  try {
239
- this.isRecording = false;
240
- this.isProcessing = true;
241
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
242
- this.updateDebugInfo('Stop Recording', {});
243
- this.recordingStateChanged.emit({
244
- isRecording: false,
245
- state: 'processing'
246
- });
247
- // Stop recording and get audio blob
248
384
  const audioBlob = await this.audioRecorder.stopRecording();
249
- const audioContent = new File([audioBlob], 'audio.webm', { type: 'audio/webm' });
250
- this.processAudioContent(audioContent);
385
+ const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
386
+ // processAudioContent manages isProcessing itself; await ensures we don't return early
387
+ await this.processAudioContent(audioContent);
251
388
  }
252
389
  catch (error) {
253
- this.hasError = true;
254
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
255
- this.updateDebugInfo('Processing Error', { error: error.message });
256
- this.formFilled.emit({
257
- success: false,
258
- error: error.message,
259
- transcription: this.transcription
260
- });
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 });
261
398
  }
262
399
  finally {
263
- this.isProcessing = false;
264
- this.recordingStateChanged.emit({
265
- isRecording: false,
266
- state: 'idle'
267
- });
400
+ this.recordingStateChanged.emit({ isRecording: false, state: 'idle' });
268
401
  }
269
402
  }
270
403
  extractFilledData(filledData) {
@@ -574,12 +707,14 @@ export class VoiceFormRecorder {
574
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" })))));
575
708
  }
576
709
  renderStatusMessage() {
710
+ const hasAnyError = this.hasError || this.hasTransientError;
577
711
  const statusClass = [
578
712
  'status-text',
579
- this.hasError && 'error',
580
- this.filledData && !this.hasError && 'success'
713
+ hasAnyError && 'error',
714
+ this.filledData && !hasAnyError && 'success'
581
715
  ].filter(Boolean).join(' ');
582
- return h("div", { class: statusClass }, this.statusMessage);
716
+ const statusStyle = this.getStatusStyle();
717
+ return h("div", { class: statusClass, style: statusStyle }, this.statusMessage);
583
718
  }
584
719
  renderFormPreview() {
585
720
  if (!this.parsedSchema)
@@ -752,8 +887,7 @@ export class VoiceFormRecorder {
752
887
  }
753
888
  render() {
754
889
  const containerStyle = this.getContainerStyle();
755
- const statusStyle = this.getStatusStyle();
756
- return (h("div", { key: '8503450dbd4de2cb91172bd2bdbdf16a0c4cdce0' }, h("div", { key: '8b3f553574a64754312f68f2abe0d64f57c56d3e', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h("div", { key: '3d08a37d611bc91dbe11c065c633f0d36c8b362e', 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() : "")));
757
891
  }
758
892
  static get is() { return "voice-input-module"; }
759
893
  static get encapsulation() { return "shadow"; }
@@ -888,6 +1022,58 @@ export class VoiceFormRecorder {
888
1022
  "reflect": false,
889
1023
  "defaultValue": "'gpt-5-mini'"
890
1024
  },
1025
+ "transcriptionProvider": {
1026
+ "type": "string",
1027
+ "attribute": "transcription-provider",
1028
+ "mutable": false,
1029
+ "complexType": {
1030
+ "original": "TranscriptionProvider",
1031
+ "resolved": "\"mistral\" | \"openai\"",
1032
+ "references": {
1033
+ "TranscriptionProvider": {
1034
+ "location": "import",
1035
+ "path": "../../types/service-providers.types",
1036
+ "id": "src/types/service-providers.types.ts::TranscriptionProvider"
1037
+ }
1038
+ }
1039
+ },
1040
+ "required": false,
1041
+ "optional": false,
1042
+ "docs": {
1043
+ "tags": [],
1044
+ "text": ""
1045
+ },
1046
+ "getter": false,
1047
+ "setter": false,
1048
+ "reflect": false,
1049
+ "defaultValue": "'openai'"
1050
+ },
1051
+ "completionProvider": {
1052
+ "type": "string",
1053
+ "attribute": "completion-provider",
1054
+ "mutable": false,
1055
+ "complexType": {
1056
+ "original": "CompletionProvider",
1057
+ "resolved": "\"anthropic\" | \"mistral\" | \"openai\"",
1058
+ "references": {
1059
+ "CompletionProvider": {
1060
+ "location": "import",
1061
+ "path": "../../types/service-providers.types",
1062
+ "id": "src/types/service-providers.types.ts::CompletionProvider"
1063
+ }
1064
+ }
1065
+ },
1066
+ "required": false,
1067
+ "optional": false,
1068
+ "docs": {
1069
+ "tags": [],
1070
+ "text": ""
1071
+ },
1072
+ "getter": false,
1073
+ "setter": false,
1074
+ "reflect": false,
1075
+ "defaultValue": "'openai'"
1076
+ },
891
1077
  "context": {
892
1078
  "type": "string",
893
1079
  "attribute": "context",
@@ -1056,6 +1242,7 @@ export class VoiceFormRecorder {
1056
1242
  "isProcessing": {},
1057
1243
  "statusMessage": {},
1058
1244
  "hasError": {},
1245
+ "hasTransientError": {},
1059
1246
  "transcription": {},
1060
1247
  "filledData": {},
1061
1248
  "debugInfo": {},
@@ -1219,6 +1406,18 @@ export class VoiceFormRecorder {
1219
1406
  }, {
1220
1407
  "propName": "theme",
1221
1408
  "methodName": "initializeServices"
1409
+ }, {
1410
+ "propName": "transcriptionProvider",
1411
+ "methodName": "initializeServices"
1412
+ }, {
1413
+ "propName": "completionProvider",
1414
+ "methodName": "initializeServices"
1415
+ }, {
1416
+ "propName": "transcriptionModel",
1417
+ "methodName": "initializeServices"
1418
+ }, {
1419
+ "propName": "completionModel",
1420
+ "methodName": "initializeServices"
1222
1421
  }];
1223
1422
  }
1224
1423
  }