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.
Files changed (28) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/voice-input-module.cjs.entry.js +237 -96
  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.css +34 -0
  7. package/dist/collection/components/voice-input-module/voice-input-module.js +239 -96
  8. package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -1
  9. package/dist/components/voice-input-module.js +239 -96
  10. package/dist/components/voice-input-module.js.map +1 -1
  11. package/dist/esm/loader.js +1 -1
  12. package/dist/esm/voice-input-module.entry.js +237 -96
  13. package/dist/esm/voice-input-module.entry.js.map +1 -1
  14. package/dist/esm/voice-input-module.js +1 -1
  15. package/dist/types/components/voice-input-module/voice-input-module.d.ts +6 -0
  16. package/dist/voice-input-module/{p-0e2b9ca0.entry.js → p-b3bf4feb.entry.js} +3 -3
  17. package/dist/voice-input-module/p-b3bf4feb.entry.js.map +1 -0
  18. package/dist/voice-input-module/voice-input-module.entry.esm.js.map +1 -1
  19. package/dist/voice-input-module/voice-input-module.esm.js +1 -1
  20. package/package.json +1 -1
  21. package/www/build/{p-812b92c7.js → p-3aeb495d.js} +1 -1
  22. package/www/build/{p-0e2b9ca0.entry.js → p-b3bf4feb.entry.js} +3 -3
  23. package/www/build/p-b3bf4feb.entry.js.map +1 -0
  24. package/www/build/voice-input-module.entry.esm.js.map +1 -1
  25. package/www/build/voice-input-module.esm.js +1 -1
  26. package/www/index.html +1 -1
  27. package/dist/voice-input-module/p-0e2b9ca0.entry.js.map +0 -1
  28. package/www/build/p-0e2b9ca0.entry.js.map +0 -1
@@ -4326,7 +4326,7 @@ const defineCustomElements = async (win, options) => {
4326
4326
 
4327
4327
  (function(){if("undefined"!==typeof window&&void 0!==window.Reflect&&void 0!==window.customElements){var a=HTMLElement;window.HTMLElement=function(){return Reflect.construct(a,[],this.constructor)};HTMLElement.prototype=a.prototype;HTMLElement.prototype.constructor=HTMLElement;Object.setPrototypeOf(HTMLElement,a);}})();
4328
4328
 
4329
- const voiceInputModuleCss = ":host{display:block;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif}.row-audio-area{display:flex;flex-direction:row;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}.voice-recorder-container{display:flex;flex-direction:column;align-items:center;gap:1rem;padding:1rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#ffffff;margin:0 auto}.voice-recorder-container-debug{display:flex;flex-direction:column;align-items:center;gap:1rem;padding:1rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#ffffff;margin:0 auto}.upload-record-container{display:inline-flex;cursor:pointer}.upload-record-button{display:flex;align-items:center;justify-content:center;transition:transform 0.2s ease, box-shadow 0.2s ease;user-select:none}.upload-record-button svg{width:100%;height:100%;transition:transform 0.2s ease}.upload-record-container:hover .upload-record-button svg{transform:scale(1.05)}.upload-record-button svg path{}.record-button{display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;background:#ee4444;color:white;cursor:pointer;transition:all 0.2s ease;position:relative;overflow:hidden;flex-shrink:0}.record-button:hover:not(:disabled){background:#dd3333;transform:scale(1.05)}.record-button:disabled{opacity:0.6;cursor:not-allowed}.record-button.recording{background:#dd3333;animation:pulse 1.5s infinite}.record-button.processing{background:#3b82f6}@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(239, 68, 68, 0.7)}70%{box-shadow:0 0 0 10px rgba(239, 68, 68, 0)}100%{box-shadow:0 0 0 0 rgba(239, 68, 68, 0)}}.record-icon{width:24px;height:24px;fill:currentColor}.status-text{font-size:0.875rem;color:#6b7280;text-align:center;min-height:1.25rem}.status-text.error{color:#ee4444}.status-text.success{color:#10b981}.debug-panel{width:100%;margin-top:1rem;padding:1rem;background:#f9fafb;border:1px solid #e5e7eb;border-radius:0.375rem;font-family:'Monaco', 'Menlo', 'Ubuntu Mono', monospace;font-size:0.75rem}.debug-title{font-weight:600;margin-bottom:0.5rem;color:#374151}.debug-content{white-space:pre-wrap;word-break:break-word;color:#6b7280;max-height:200px;overflow-y:auto}.permissions-warning{padding:0.75rem;background:#fef3c7;border:1px solid #f59e0b;border-radius:0.375rem;color:#92400e;font-size:0.875rem;text-align:center}.form-preview{width:100%;margin-top:1rem;padding:1rem;background:#f8fafc;border:1px solid #e2e8f0;border-radius:0.375rem}.form-preview-title{font-weight:600;margin-bottom:0.5rem;color:#1e293b}.form-field{margin-bottom:0.5rem;font-size:0.875rem}.field-name{font-weight:500;color:#475569}.field-value{color:#64748b;margin-left:0.5rem}.field-value.filled{color:#059669;font-weight:500}.voice-filled-form{display:grid;grid-template-columns:repeat(auto-fit, minmax(250px, 1fr));gap:0.5rem;margin-top:1rem}.form-group{display:flex;flex-direction:column;gap:0.15rem}.form-group.full-width{grid-column:1 / -1}.form-header{grid-column:1 / -1;font-weight:600;font-size:1rem;color:#1e293b;margin-top:0.75rem;margin-bottom:0.25rem;padding-bottom:0.25rem;border-bottom:2px solid #e2e8f0}.form-label{font-weight:500;color:#374151;font-size:0.8rem}.required{color:#ee4444;margin-left:0.125rem}.form-input{padding:0.4rem;border:1px solid #d1d5db;border-radius:0.375rem;font-size:0.8rem;transition:border-color 0.2s ease}.form-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.form-checkbox{width:auto;padding:0;margin:0}select.form-input{background-color:white;cursor:pointer}input[type=\"date\"].form-input{cursor:pointer}.readonly-select{background:#f9fafb;border:1px solid #d1d5db;border-radius:0.375rem;padding:0.5rem}.select-placeholder{font-size:0.875rem;color:#6b7280;margin-bottom:0.5rem}.select-options-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:0.25rem;max-height:150px;overflow-y:auto}.select-option{background:#e5e7eb;padding:0.25rem 0.5rem;border-radius:0.25rem;font-size:0.875rem;color:#374151}.multiselect-container{display:grid;grid-template-columns:repeat(2, 1fr);gap:0.25rem;padding:0.5rem;border:1px solid #d1d5db;border-radius:0.375rem;background:white}.multiselect-option{display:flex;align-items:center;gap:0.5rem;padding:0.5rem;border-radius:0.25rem;cursor:pointer;transition:background 0.2s ease}.multiselect-option:hover{background:#f3f4f6}.multiselect-option input[type=\"checkbox\"]{width:18px;height:18px;cursor:pointer}.multiselect-option input[type=\"checkbox\"]:checked+.multiselect-label{font-weight:600;color:#059669}.multiselect-label{font-size:0.875rem;color:#374151;user-select:none}ocr-file-uploader{display:block;width:100% !important;height:100% !important;max-width:100% !important;max-height:100% !important}ocr-file-uploader *{max-width:100% !important;max-height:100% !important}";
4329
+ const voiceInputModuleCss = ":host{display:block;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif}.row-audio-area{display:flex;flex-direction:row;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}.voice-recorder-container{display:flex;flex-direction:column;align-items:center;gap:1rem;padding:1rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#ffffff;margin:0 auto}.voice-recorder-container-debug{display:flex;flex-direction:column;align-items:center;gap:1rem;padding:1rem;border:1px solid #e5e7eb;border-radius:0.5rem;background:#ffffff;margin:0 auto}.upload-record-container{display:inline-flex;cursor:pointer}.upload-record-button{display:flex;align-items:center;justify-content:center;transition:transform 0.2s ease, box-shadow 0.2s ease;user-select:none}.upload-record-button svg{width:100%;height:100%;transition:transform 0.2s ease}.upload-record-container:hover .upload-record-button svg{transform:scale(1.05)}.upload-record-button svg path{}.record-button{display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;background:#ee4444;color:white;cursor:pointer;transition:all 0.2s ease;position:relative;overflow:hidden;flex-shrink:0}.record-button:hover:not(:disabled){background:#dd3333;transform:scale(1.05)}.record-button:disabled{opacity:0.6;cursor:not-allowed}.record-button.recording{background:#dd3333;animation:pulse 1.5s infinite}.record-button.processing{background:#3b82f6}@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(239, 68, 68, 0.7)}70%{box-shadow:0 0 0 10px rgba(239, 68, 68, 0)}100%{box-shadow:0 0 0 0 rgba(239, 68, 68, 0)}}.record-icon{width:24px;height:24px;fill:currentColor}.status-text{font-size:0.875rem;color:#6b7280;text-align:center;min-height:1.25rem}.status-text.error{color:#ee4444}.status-text.success{color:#10b981}.debug-panel{width:100%;margin-top:1rem;padding:1rem;background:#f9fafb;border:1px solid #e5e7eb;border-radius:0.375rem;font-family:'Monaco', 'Menlo', 'Ubuntu Mono', monospace;font-size:0.75rem}.debug-title{font-weight:600;margin-bottom:0.5rem;color:#374151}.debug-content{white-space:pre-wrap;word-break:break-word;color:#6b7280;max-height:200px;overflow-y:auto}.permissions-warning{padding:0.75rem;background:#fef3c7;border:1px solid #f59e0b;border-radius:0.375rem;color:#92400e;font-size:0.875rem;text-align:center}.form-preview{width:100%;margin-top:1rem;padding:1rem;background:#f8fafc;border:1px solid #e2e8f0;border-radius:0.375rem}.form-preview-title{font-weight:600;margin-bottom:0.5rem;color:#1e293b}.form-field{margin-bottom:0.5rem;font-size:0.875rem}.field-name{font-weight:500;color:#475569}.field-value{color:#64748b;margin-left:0.5rem}.field-value.filled{color:#059669;font-weight:500}.voice-filled-form{display:grid;grid-template-columns:repeat(auto-fit, minmax(250px, 1fr));gap:0.5rem;margin-top:1rem}.form-group{display:flex;flex-direction:column;gap:0.15rem}.form-group.full-width{grid-column:1 / -1}.form-header{grid-column:1 / -1;font-weight:600;font-size:1rem;color:#1e293b;margin-top:0.75rem;margin-bottom:0.25rem;padding-bottom:0.25rem;border-bottom:2px solid #e2e8f0}.form-label{font-weight:500;color:#374151;font-size:0.8rem}.required{color:#ee4444;margin-left:0.125rem}.form-input{padding:0.4rem;border:1px solid #d1d5db;border-radius:0.375rem;font-size:0.8rem;transition:border-color 0.2s ease}.form-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.form-checkbox{width:auto;padding:0;margin:0}select.form-input{background-color:white;cursor:pointer}input[type=\"date\"].form-input{cursor:pointer}.readonly-select{background:#f9fafb;border:1px solid #d1d5db;border-radius:0.375rem;padding:0.5rem}.select-placeholder{font-size:0.875rem;color:#6b7280;margin-bottom:0.5rem}.select-options-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:0.25rem;max-height:150px;overflow-y:auto}.select-option{background:#e5e7eb;padding:0.25rem 0.5rem;border-radius:0.25rem;font-size:0.875rem;color:#374151}.multiselect-container{display:grid;grid-template-columns:repeat(2, 1fr);gap:0.25rem;padding:0.5rem;border:1px solid #d1d5db;border-radius:0.375rem;background:white}.multiselect-option{display:flex;align-items:center;gap:0.5rem;padding:0.5rem;border-radius:0.25rem;cursor:pointer;transition:background 0.2s ease}.multiselect-option:hover{background:#f3f4f6}.multiselect-option input[type=\"checkbox\"]{width:18px;height:18px;cursor:pointer}.multiselect-option input[type=\"checkbox\"]:checked+.multiselect-label{font-weight:600;color:#059669}.multiselect-label{font-size:0.875rem;color:#374151;user-select:none}@keyframes skeleton-shimmer{0%{background-position:-200% 0}100%{background-position:200% 0}}.skeleton-circle{border-radius:50%;flex-shrink:0;background:linear-gradient(\r\n 90deg,\r\n #e2e8f0 25%,\r\n #f8fafc 50%,\r\n #e2e8f0 75%\r\n );background-size:200% 100%;animation:skeleton-shimmer 1.5s ease-in-out infinite}.skeleton-status{height:14px;border-radius:7px;width:140px;background:linear-gradient(\r\n 90deg,\r\n #e2e8f0 25%,\r\n #f8fafc 50%,\r\n #e2e8f0 75%\r\n );background-size:200% 100%;animation:skeleton-shimmer 1.5s ease-in-out infinite;animation-delay:0.15s}ocr-file-uploader{display:block;width:100% !important;height:100% !important;max-width:100% !important;max-height:100% !important}ocr-file-uploader *{max-width:100% !important;max-height:100% !important}";
4330
4330
 
4331
4331
  // Initialize OCR
4332
4332
  defineCustomElements();
@@ -4365,10 +4365,12 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4365
4365
  this.isRecording = false;
4366
4366
  this.isProcessing = false;
4367
4367
  this.hasError = false;
4368
+ this.hasTransientError = false;
4368
4369
  this.transcription = '';
4369
4370
  this.filledData = null;
4370
4371
  this.debugInfo = {};
4371
4372
  this.isReadonlyMode = true; // Start in readonly preview mode
4373
+ this.isInitializing = true;
4372
4374
  this.triggerAudioRecordUpload = () => {
4373
4375
  this.fileInputAudioRecord.click();
4374
4376
  };
@@ -4376,14 +4378,35 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4376
4378
  const input = event.target;
4377
4379
  if (!input.files || input.files.length === 0)
4378
4380
  return;
4381
+ if (this.isProcessing || this.isRecording) {
4382
+ input.value = '';
4383
+ return;
4384
+ }
4379
4385
  const file = input.files[0];
4380
- this.processAudioContent(file);
4386
+ const isEn = this.language === 'en';
4387
+ // Validate file type
4388
+ const validAudioTypes = /^audio\//;
4389
+ const validAudioExtensions = /\.(mp3|wav|webm|m4a|ogg|flac|aac|opus)$/i;
4390
+ if (!validAudioTypes.test(file.type) && !validAudioExtensions.test(file.name)) {
4391
+ this.hasTransientError = true;
4392
+ this.statusMessage = isEn
4393
+ ? `Invalid file type: "${file.name}". Please select an audio file.`
4394
+ : `Type de fichier invalide : "${file.name}". Veuillez sélectionner un fichier audio.`;
4395
+ input.value = '';
4396
+ return;
4397
+ }
4398
+ // Reset the input so the same file can be re-selected after an error
4399
+ input.value = '';
4400
+ await this.processAudioContent(file);
4381
4401
  };
4382
4402
  this.audioRecorder = new AudioRecorderService();
4383
4403
  }
4384
4404
  componentWillLoad() {
4385
4405
  this.initializeServices();
4386
4406
  }
4407
+ componentDidLoad() {
4408
+ this.isInitializing = false;
4409
+ }
4387
4410
  initializeServices() {
4388
4411
  var _a;
4389
4412
  // Parse theme
@@ -4437,6 +4460,7 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4437
4460
  config: this.parsedConfig
4438
4461
  });
4439
4462
  this.hasError = false;
4463
+ this.hasTransientError = false;
4440
4464
  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');
4441
4465
  if (this.parsedInputTypes.length === 0) {
4442
4466
  this.inputTypes = 'voice';
@@ -4457,6 +4481,91 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4457
4481
  } });
4458
4482
  }
4459
4483
  }
4484
+ getMicErrorMessage(error) {
4485
+ var _a, _b;
4486
+ const isEn = this.language === 'en';
4487
+ const errorName = error.name || '';
4488
+ if (errorName === 'NotAllowedError' || errorName === 'PermissionDeniedError') {
4489
+ return isEn
4490
+ ? 'Microphone access denied. Allow microphone access in your browser settings and try again.'
4491
+ : "Accès au microphone refusé. Autorisez l'accès au microphone dans les paramètres du navigateur et réessayez.";
4492
+ }
4493
+ if (errorName === 'NotFoundError' || errorName === 'DevicesNotFoundError') {
4494
+ return isEn
4495
+ ? 'No microphone found. Please connect a microphone and try again.'
4496
+ : 'Aucun microphone trouvé. Connectez un microphone et réessayez.';
4497
+ }
4498
+ if (errorName === 'NotReadableError' || errorName === 'TrackStartError') {
4499
+ return isEn
4500
+ ? 'Microphone is busy or unavailable. Close other apps using the microphone and try again.'
4501
+ : 'Le microphone est occupé ou indisponible. Fermez les autres applications utilisant le microphone et réessayez.';
4502
+ }
4503
+ if (errorName === 'AbortError') {
4504
+ return isEn
4505
+ ? 'Microphone access was interrupted. Please try again.'
4506
+ : "L'accès au microphone a été interrompu. Veuillez réessayer.";
4507
+ }
4508
+ if (errorName === 'OverconstrainedError') {
4509
+ return isEn
4510
+ ? 'Microphone does not support the required audio settings.'
4511
+ : 'Le microphone ne supporte pas les paramètres audio requis.';
4512
+ }
4513
+ 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'))) {
4514
+ return isEn
4515
+ ? 'Microphone access requires HTTPS or localhost.'
4516
+ : "L'accès au microphone nécessite HTTPS ou localhost.";
4517
+ }
4518
+ return isEn ? `Microphone error: ${error.message}` : `Erreur microphone : ${error.message}`;
4519
+ }
4520
+ getReadableErrorMessage(error, phase) {
4521
+ const isEn = this.language === 'en';
4522
+ const msg = error.message || '';
4523
+ if (/401|unauthorized|invalid.{0,20}(api )?key|authentication failed/i.test(msg)) {
4524
+ return isEn
4525
+ ? 'Authentication failed: invalid API key. Check your configuration.'
4526
+ : "Échec d'authentification : clé API invalide. Vérifiez votre configuration.";
4527
+ }
4528
+ if (/403|forbidden/i.test(msg)) {
4529
+ return isEn
4530
+ ? 'Access denied: check your API permissions.'
4531
+ : 'Accès refusé : vérifiez vos permissions API.';
4532
+ }
4533
+ if (/429|rate.{0,10}limit|too many requests/i.test(msg)) {
4534
+ return isEn
4535
+ ? 'Rate limit exceeded. Please wait a moment and try again.'
4536
+ : 'Limite de requêtes dépassée. Veuillez attendre quelques secondes et réessayer.';
4537
+ }
4538
+ if (/404|not found/i.test(msg)) {
4539
+ return isEn
4540
+ ? 'API endpoint not found. Check your proxy URL configuration.'
4541
+ : "Endpoint API introuvable. Vérifiez l'URL de votre proxy.";
4542
+ }
4543
+ if (/5\d\d|server error|internal server|bad gateway|service unavailable/i.test(msg)) {
4544
+ return isEn
4545
+ ? 'Server error. Please try again in a moment.'
4546
+ : 'Erreur serveur. Veuillez réessayer dans un instant.';
4547
+ }
4548
+ if (/network|failed to fetch|err_connection|connection refused/i.test(msg)) {
4549
+ return isEn
4550
+ ? 'Network error: check your connection and proxy server.'
4551
+ : 'Erreur réseau : vérifiez votre connexion et le serveur proxy.';
4552
+ }
4553
+ if (/json|parse error|unexpected token|invalid response/i.test(msg)) {
4554
+ return isEn
4555
+ ? 'Unexpected response from AI service. Please try again.'
4556
+ : 'Réponse inattendue du service IA. Veuillez réessayer.';
4557
+ }
4558
+ if (phase === 'transcription') {
4559
+ return isEn ? `Transcription failed: ${msg}` : `Échec de la transcription : ${msg}`;
4560
+ }
4561
+ if (phase === 'llm') {
4562
+ return isEn ? `Form filling failed: ${msg}` : `Échec du remplissage du formulaire : ${msg}`;
4563
+ }
4564
+ if (phase === 'ocr') {
4565
+ return isEn ? `OCR processing failed: ${msg}` : `Échec du traitement OCR : ${msg}`;
4566
+ }
4567
+ return isEn ? `Processing error: ${msg}` : `Erreur de traitement : ${msg}`;
4568
+ }
4460
4569
  async handleRecordClick() {
4461
4570
  if (this.isProcessing)
4462
4571
  return;
@@ -4470,7 +4579,7 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4470
4579
  async startRecording() {
4471
4580
  var _a;
4472
4581
  try {
4473
- this.hasError = false;
4582
+ this.hasTransientError = false;
4474
4583
  this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
4475
4584
  this.updateDebugInfo('Start Recording Attempt', {});
4476
4585
  await this.audioRecorder.startRecording();
@@ -4483,8 +4592,8 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4483
4592
  });
4484
4593
  }
4485
4594
  catch (error) {
4486
- this.hasError = true;
4487
- this.statusMessage = (this.language == 'en' ? `Recording failed: ${error.message}` : `Echec de l'enregistrement : ${error.message}`);
4595
+ this.hasTransientError = true;
4596
+ this.statusMessage = this.getMicErrorMessage(error);
4488
4597
  this.updateDebugInfo('Recording Error', { error: error.message });
4489
4598
  this.formFilled.emit({
4490
4599
  success: false,
@@ -4494,40 +4603,38 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4494
4603
  }
4495
4604
  async processJsonForm(ocrData) {
4496
4605
  var _a, _b, _c;
4606
+ if (this.isProcessing || this.isRecording)
4607
+ return;
4608
+ this.isProcessing = true;
4609
+ this.hasTransientError = false;
4610
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing document...' : 'Traitement du document ...');
4497
4611
  try {
4498
- this.isProcessing = true;
4499
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing json...' : `Traitement du json ...`);
4500
- // Extract content from OCR format
4501
4612
  const extractedData = (ocrData === null || ocrData === void 0 ? void 0 : ocrData.content) || ocrData;
4502
4613
  const jsonForm = JSON.stringify(extractedData);
4503
- // Fill form using LLM
4614
+ if (!extractedData) {
4615
+ throw Object.assign(new Error('OCR returned no data'), { phase: 'ocr' });
4616
+ }
4504
4617
  this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4505
4618
  const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4506
- const filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
4507
- // Extract filled data
4619
+ let filledSchema;
4620
+ try {
4621
+ filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
4622
+ }
4623
+ catch (llmError) {
4624
+ throw Object.assign(new Error(llmError.message), { phase: 'llm' });
4625
+ }
4508
4626
  this.filledData = this.extractFilledData(filledSchema);
4509
- this.updateDebugInfo('Form Filled', {
4510
- filledSchema,
4511
- extractedData: this.filledData
4512
- });
4627
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
4513
4628
  this.parsedSchema = this.filledData;
4514
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
4515
- this.hasError = false;
4516
- // Emit success event
4517
- this.formFilled.emit({
4518
- success: true,
4519
- data: this.filledData,
4520
- jsonForm: jsonForm
4521
- });
4629
+ this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
4630
+ this.formFilled.emit({ success: true, data: this.filledData, jsonForm });
4522
4631
  }
4523
4632
  catch (error) {
4524
- this.hasError = true;
4525
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
4526
- this.formFilled.emit({
4527
- success: false,
4528
- error: error.message,
4529
- jsonForm: JSON.stringify(ocrData)
4530
- });
4633
+ console.error('OCR processing error:', error);
4634
+ this.hasTransientError = true;
4635
+ this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'llm');
4636
+ this.updateDebugInfo('OCR Processing Error', { phase: error.phase, error: error.message });
4637
+ this.formFilled.emit({ success: false, error: error.message, jsonForm: JSON.stringify(ocrData) });
4531
4638
  }
4532
4639
  finally {
4533
4640
  this.isProcessing = false;
@@ -4535,83 +4642,102 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4535
4642
  }
4536
4643
  async processAudioContent(audioFile) {
4537
4644
  var _a, _b, _c;
4538
- this.updateDebugInfo('Audio Captured', {
4539
- size: audioFile.size,
4540
- type: audioFile.type
4541
- });
4645
+ if (this.isProcessing || this.isRecording)
4646
+ return;
4647
+ this.isProcessing = true;
4648
+ this.hasTransientError = false;
4649
+ this.updateDebugInfo('Audio Captured', { size: audioFile.size, type: audioFile.type });
4542
4650
  try {
4543
- // Transcribe audio
4544
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
4545
- const transcription = await this.speechToTextService.transcribe(audioFile, this.language);
4546
- this.transcription = transcription;
4547
- this.updateDebugInfo('Transcription Complete', { transcription });
4651
+ // Validate audio file before sending to API
4652
+ const MAX_AUDIO_SIZE = 25 * 1024 * 1024; // 25MB (API limit)
4653
+ if (audioFile.size <= 44) { // WAV header alone is 44 bytes; effectively empty
4654
+ this.hasTransientError = true;
4655
+ this.statusMessage = this.language == 'en'
4656
+ ? 'Recording is empty or too short. Please try again.'
4657
+ : "L'enregistrement est vide ou trop court. Veuillez réessayer.";
4658
+ this.formFilled.emit({ success: false, error: 'Empty or too short audio' });
4659
+ return;
4660
+ }
4661
+ if (audioFile.size > MAX_AUDIO_SIZE) {
4662
+ this.hasTransientError = true;
4663
+ this.statusMessage = this.language == 'en'
4664
+ ? `File too large (${Math.round(audioFile.size / 1024 / 1024)}MB). Maximum is 25MB.`
4665
+ : `Fichier trop volumineux (${Math.round(audioFile.size / 1024 / 1024)}Mo). Maximum : 25Mo.`;
4666
+ this.formFilled.emit({ success: false, error: 'Audio file too large' });
4667
+ return;
4668
+ }
4669
+ // Step 1: Transcription
4670
+ let transcription;
4671
+ try {
4672
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
4673
+ transcription = await this.speechToTextService.transcribe(audioFile, this.language);
4674
+ this.transcription = transcription;
4675
+ this.updateDebugInfo('Transcription Complete', { transcription });
4676
+ }
4677
+ catch (transcriptionError) {
4678
+ throw Object.assign(new Error(transcriptionError.message), { phase: 'transcription' });
4679
+ }
4548
4680
  if (!transcription.trim()) {
4549
- throw new Error('No speech detected in the recording');
4681
+ this.hasTransientError = true;
4682
+ this.statusMessage = this.language == 'en'
4683
+ ? 'No speech detected. Please speak clearly and try again.'
4684
+ : 'Aucune parole détectée. Parlez clairement et réessayez.';
4685
+ this.formFilled.emit({ success: false, error: 'No speech detected', transcription: '' });
4686
+ return;
4687
+ }
4688
+ // Step 2: LLM form filling
4689
+ let filledSchema;
4690
+ try {
4691
+ this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4692
+ const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4693
+ filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
4694
+ }
4695
+ catch (llmError) {
4696
+ throw Object.assign(new Error(llmError.message), { phase: 'llm' });
4550
4697
  }
4551
- // Fill form using LLM
4552
- this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4553
- const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4554
- const filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
4555
- // Extract filled data
4556
4698
  this.filledData = this.extractFilledData(filledSchema);
4557
- this.updateDebugInfo('Form Filled', {
4558
- filledSchema,
4559
- extractedData: this.filledData
4560
- });
4699
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
4561
4700
  this.parsedSchema = this.filledData;
4562
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
4563
- this.hasError = false;
4564
- // Emit success event
4565
- this.formFilled.emit({
4566
- success: true,
4567
- data: this.filledData,
4568
- transcription: transcription
4569
- });
4701
+ this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
4702
+ this.formFilled.emit({ success: true, data: this.filledData, transcription });
4570
4703
  }
4571
4704
  catch (error) {
4572
- console.error('Transcription error:', error);
4573
- this.hasError = true;
4574
- this.statusMessage = (this.language == 'en' ? 'Audio processing failed.' : 'Échec du traitement audio.');
4575
- this.updateDebugInfo('Transcription Error', { error: error.message });
4576
- this.formFilled.emit({
4577
- success: false,
4578
- error: error.message,
4579
- transcription: this.transcription
4580
- });
4705
+ console.error('Audio processing error:', error);
4706
+ this.hasTransientError = true;
4707
+ this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'audio-upload');
4708
+ this.updateDebugInfo('Audio Processing Error', { phase: error.phase, error: error.message });
4709
+ this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
4710
+ }
4711
+ finally {
4712
+ this.isProcessing = false;
4581
4713
  }
4582
4714
  }
4583
4715
  async stopRecordingAndProcess() {
4584
4716
  var _a;
4717
+ this.isRecording = false;
4718
+ this.isProcessing = true;
4719
+ this.hasTransientError = false;
4720
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
4721
+ this.updateDebugInfo('Stop Recording', {});
4722
+ this.recordingStateChanged.emit({ isRecording: false, state: 'processing' });
4585
4723
  try {
4586
- this.isRecording = false;
4587
- this.isProcessing = true;
4588
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
4589
- this.updateDebugInfo('Stop Recording', {});
4590
- this.recordingStateChanged.emit({
4591
- isRecording: false,
4592
- state: 'processing'
4593
- });
4594
- // Stop recording and get audio blob
4595
4724
  const audioBlob = await this.audioRecorder.stopRecording();
4596
4725
  const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
4597
- this.processAudioContent(audioContent);
4726
+ // processAudioContent manages isProcessing itself; await ensures we don't return early
4727
+ await this.processAudioContent(audioContent);
4598
4728
  }
4599
4729
  catch (error) {
4600
- this.hasError = true;
4601
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
4602
- this.updateDebugInfo('Processing Error', { error: error.message });
4603
- this.formFilled.emit({
4604
- success: false,
4605
- error: error.message,
4606
- transcription: this.transcription
4607
- });
4730
+ // Only reached if audioRecorder.stopRecording() itself throws
4731
+ this.hasTransientError = true;
4732
+ this.isProcessing = false;
4733
+ this.statusMessage = (this.language == 'en'
4734
+ ? `Failed to stop recording: ${error.message}`
4735
+ : `Impossible d'arrêter l'enregistrement : ${error.message}`);
4736
+ this.updateDebugInfo('Stop Recording Error', { error: error.message });
4737
+ this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
4608
4738
  }
4609
4739
  finally {
4610
- this.isProcessing = false;
4611
- this.recordingStateChanged.emit({
4612
- isRecording: false,
4613
- state: 'idle'
4614
- });
4740
+ this.recordingStateChanged.emit({ isRecording: false, state: 'idle' });
4615
4741
  }
4616
4742
  }
4617
4743
  extractFilledData(filledData) {
@@ -4834,7 +4960,7 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4834
4960
  textColor: ocrStyle.textColor || '#ffffff'
4835
4961
  }
4836
4962
  };
4837
- const isDisabled = this.isProcessing || this.hasError;
4963
+ const isDisabled = this.isProcessing || this.hasError || this.isRecording;
4838
4964
  return (h$1("div", { style: { width: size, height: size, flexShrink: '0', opacity: isDisabled ? '0.6' : '1', pointerEvents: isDisabled ? 'none' : 'auto' } }, h$1("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%' } })));
4839
4965
  }
4840
4966
  convertSchemaToOcrFormat(schema) {
@@ -4889,7 +5015,7 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4889
5015
  return;
4890
5016
  const audioStyle = ((_a = this.parsedTheme.buttons) === null || _a === void 0 ? void 0 : _a.audio) || {};
4891
5017
  const size = audioStyle.size || '50px';
4892
- const isDisabled = this.isProcessing || this.hasError;
5018
+ const isDisabled = this.isProcessing || this.hasError || this.isRecording;
4893
5019
  const buttonStyle = {
4894
5020
  stroke: audioStyle.backgroundColor || '#3b82f6',
4895
5021
  fill: audioStyle.backgroundColor || '#3b82f6',
@@ -4918,13 +5044,26 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
4918
5044
  };
4919
5045
  return (h$1("button", { class: buttonClass, style: buttonStyle, onClick: () => this.handleRecordClick(), disabled: isDisabled, "aria-label": this.isRecording ? 'Stop recording' : 'Start recording' }, this.isProcessing ? (h$1("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h$1("circle", { cx: "12", cy: "12", r: "3" }, h$1("animate", { attributeName: "r", values: "3;6;3", dur: "1s", repeatCount: "indefinite" }), h$1("animate", { attributeName: "opacity", values: "1;0.3;1", dur: "1s", repeatCount: "indefinite" })))) : this.isRecording ? (h$1("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h$1("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }))) : (h$1("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h$1("circle", { cx: "12", cy: "12", r: "8" })))));
4920
5046
  }
5047
+ renderLoadingSkeleton() {
5048
+ var _a;
5049
+ const types = this.parsedInputTypes.length > 0 ? this.parsedInputTypes : ['voice'];
5050
+ const containerStyle = this.getContainerStyle();
5051
+ const buttons = (((_a = this.parsedTheme) === null || _a === void 0 ? void 0 : _a.buttons) || {});
5052
+ 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'; };
5053
+ return (h$1("div", null, h$1("div", { class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h$1("div", { class: "row-audio-area" }, types.map(type => {
5054
+ const size = getSize(type);
5055
+ return h$1("div", { class: "skeleton-circle", style: { width: size, height: size } });
5056
+ })), this.displayStatus && h$1("div", { class: "skeleton-status" }))));
5057
+ }
4921
5058
  renderStatusMessage() {
5059
+ const hasAnyError = this.hasError || this.hasTransientError;
4922
5060
  const statusClass = [
4923
5061
  'status-text',
4924
- this.hasError && 'error',
4925
- this.filledData && !this.hasError && 'success'
5062
+ hasAnyError && 'error',
5063
+ this.filledData && !hasAnyError && 'success'
4926
5064
  ].filter(Boolean).join(' ');
4927
- return h$1("div", { class: statusClass }, this.statusMessage);
5065
+ const statusStyle = this.getStatusStyle();
5066
+ return h$1("div", { class: statusClass, style: statusStyle }, this.statusMessage);
4928
5067
  }
4929
5068
  renderFormPreview() {
4930
5069
  if (!this.parsedSchema)
@@ -5096,9 +5235,11 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
5096
5235
  };
5097
5236
  }
5098
5237
  render() {
5238
+ if (this.isInitializing) {
5239
+ return this.renderLoadingSkeleton();
5240
+ }
5099
5241
  const containerStyle = this.getContainerStyle();
5100
- const statusStyle = this.getStatusStyle();
5101
- return (h$1("div", { key: '8378e98f02e5b7482929d255c5ec12ff8c2731e4' }, h$1("div", { key: 'b8a0d873bd4e1b4c8936747c0919ac8c4c15301b', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h$1("div", { key: '86e7783e3686db378ee16aa91a640f33c3255923', class: "row-audio-area" }, this.renderRecordButton(), this.renderUploadRecordButton(), this.renderUploadButton()), this.displayStatus ? h$1("div", { class: "status-text", style: statusStyle }, this.statusMessage) : "", this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
5242
+ return (h$1("div", null, h$1("div", { class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h$1("div", { class: "row-audio-area" }, this.renderRecordButton(), this.renderUploadRecordButton(), this.renderUploadButton()), this.displayStatus ? this.renderStatusMessage() : "", this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
5102
5243
  }
5103
5244
  static get watchers() { return {
5104
5245
  "formJson": ["initializeServices"],
@@ -5131,10 +5272,12 @@ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecord
5131
5272
  "isProcessing": [32],
5132
5273
  "statusMessage": [32],
5133
5274
  "hasError": [32],
5275
+ "hasTransientError": [32],
5134
5276
  "transcription": [32],
5135
5277
  "filledData": [32],
5136
5278
  "debugInfo": [32],
5137
5279
  "isReadonlyMode": [32],
5280
+ "isInitializing": [32],
5138
5281
  "convertXmlToJson": [64],
5139
5282
  "convertJsonToXml": [64],
5140
5283
  "convertXmlToJsonLegacy": [64],