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
@@ -5,7 +5,7 @@ import { g as globalScripts } from './app-globals-DQuL1Twl.js';
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await globalScripts();
8
- return bootstrapLazy([["ocr-file-uploader",[[257,"ocr-file-uploader",{"batch":[4],"callback":[16],"jsonSchema":[1,"json-schema"],"theme":[1],"parsedTheme":[32]},null,{"theme":["parseTheme"]}]]],["voice-input-module",[[257,"voice-input-module",{"formJson":[1,"form-json"],"serviceConfig":[1,"service-config"],"apiKey":[1,"api-key"],"apiProxyUrl":[1,"api-proxy-url"],"transcriptionModel":[1,"transcription-model"],"completionModel":[1,"completion-model"],"transcriptionProvider":[1,"transcription-provider"],"completionProvider":[1,"completion-provider"],"context":[1],"classificationRootUrl":[1,"classification-root-url"],"language":[1],"inputTypes":[1,"input-types"],"theme":[1],"debug":[4],"renderForm":[4,"render-form"],"displayStatus":[4,"display-status"],"isRecording":[32],"isProcessing":[32],"statusMessage":[32],"hasError":[32],"transcription":[32],"filledData":[32],"debugInfo":[32],"isReadonlyMode":[32],"convertXmlToJson":[64],"convertJsonToXml":[64],"convertXmlToJsonLegacy":[64],"convertJsonToXmlLegacy":[64]},null,{"formJson":["initializeServices"],"serviceConfig":["initializeServices"],"theme":["initializeServices"],"transcriptionProvider":["initializeServices"],"completionProvider":["initializeServices"],"transcriptionModel":["initializeServices"],"completionModel":["initializeServices"]}]]]], options);
8
+ return bootstrapLazy([["ocr-file-uploader",[[257,"ocr-file-uploader",{"batch":[4],"callback":[16],"jsonSchema":[1,"json-schema"],"theme":[1],"parsedTheme":[32]},null,{"theme":["parseTheme"]}]]],["voice-input-module",[[257,"voice-input-module",{"formJson":[1,"form-json"],"serviceConfig":[1,"service-config"],"apiKey":[1,"api-key"],"apiProxyUrl":[1,"api-proxy-url"],"transcriptionModel":[1,"transcription-model"],"completionModel":[1,"completion-model"],"transcriptionProvider":[1,"transcription-provider"],"completionProvider":[1,"completion-provider"],"context":[1],"classificationRootUrl":[1,"classification-root-url"],"language":[1],"inputTypes":[1,"input-types"],"theme":[1],"debug":[4],"renderForm":[4,"render-form"],"displayStatus":[4,"display-status"],"isRecording":[32],"isProcessing":[32],"statusMessage":[32],"hasError":[32],"hasTransientError":[32],"transcription":[32],"filledData":[32],"debugInfo":[32],"isReadonlyMode":[32],"isInitializing":[32],"convertXmlToJson":[64],"convertJsonToXml":[64],"convertXmlToJsonLegacy":[64],"convertJsonToXmlLegacy":[64]},null,{"formJson":["initializeServices"],"serviceConfig":["initializeServices"],"theme":["initializeServices"],"transcriptionProvider":["initializeServices"],"completionProvider":["initializeServices"],"transcriptionModel":["initializeServices"],"completionModel":["initializeServices"]}]]]], options);
9
9
  };
10
10
 
11
11
  export { defineCustomElements };
@@ -4325,7 +4325,7 @@ const defineCustomElements = async (win, options) => {
4325
4325
 
4326
4326
  (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);}})();
4327
4327
 
4328
- 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}";
4328
+ 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}";
4329
4329
 
4330
4330
  // Initialize OCR
4331
4331
  defineCustomElements();
@@ -4362,10 +4362,12 @@ const VoiceFormRecorder = class {
4362
4362
  this.isRecording = false;
4363
4363
  this.isProcessing = false;
4364
4364
  this.hasError = false;
4365
+ this.hasTransientError = false;
4365
4366
  this.transcription = '';
4366
4367
  this.filledData = null;
4367
4368
  this.debugInfo = {};
4368
4369
  this.isReadonlyMode = true; // Start in readonly preview mode
4370
+ this.isInitializing = true;
4369
4371
  this.triggerAudioRecordUpload = () => {
4370
4372
  this.fileInputAudioRecord.click();
4371
4373
  };
@@ -4373,14 +4375,35 @@ const VoiceFormRecorder = class {
4373
4375
  const input = event.target;
4374
4376
  if (!input.files || input.files.length === 0)
4375
4377
  return;
4378
+ if (this.isProcessing || this.isRecording) {
4379
+ input.value = '';
4380
+ return;
4381
+ }
4376
4382
  const file = input.files[0];
4377
- this.processAudioContent(file);
4383
+ const isEn = this.language === 'en';
4384
+ // Validate file type
4385
+ const validAudioTypes = /^audio\//;
4386
+ const validAudioExtensions = /\.(mp3|wav|webm|m4a|ogg|flac|aac|opus)$/i;
4387
+ if (!validAudioTypes.test(file.type) && !validAudioExtensions.test(file.name)) {
4388
+ this.hasTransientError = true;
4389
+ this.statusMessage = isEn
4390
+ ? `Invalid file type: "${file.name}". Please select an audio file.`
4391
+ : `Type de fichier invalide : "${file.name}". Veuillez sélectionner un fichier audio.`;
4392
+ input.value = '';
4393
+ return;
4394
+ }
4395
+ // Reset the input so the same file can be re-selected after an error
4396
+ input.value = '';
4397
+ await this.processAudioContent(file);
4378
4398
  };
4379
4399
  this.audioRecorder = new AudioRecorderService();
4380
4400
  }
4381
4401
  componentWillLoad() {
4382
4402
  this.initializeServices();
4383
4403
  }
4404
+ componentDidLoad() {
4405
+ this.isInitializing = false;
4406
+ }
4384
4407
  initializeServices() {
4385
4408
  var _a;
4386
4409
  // Parse theme
@@ -4434,6 +4457,7 @@ const VoiceFormRecorder = class {
4434
4457
  config: this.parsedConfig
4435
4458
  });
4436
4459
  this.hasError = false;
4460
+ this.hasTransientError = false;
4437
4461
  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');
4438
4462
  if (this.parsedInputTypes.length === 0) {
4439
4463
  this.inputTypes = 'voice';
@@ -4454,6 +4478,91 @@ const VoiceFormRecorder = class {
4454
4478
  } });
4455
4479
  }
4456
4480
  }
4481
+ getMicErrorMessage(error) {
4482
+ var _a, _b;
4483
+ const isEn = this.language === 'en';
4484
+ const errorName = error.name || '';
4485
+ if (errorName === 'NotAllowedError' || errorName === 'PermissionDeniedError') {
4486
+ return isEn
4487
+ ? 'Microphone access denied. Allow microphone access in your browser settings and try again.'
4488
+ : "Accès au microphone refusé. Autorisez l'accès au microphone dans les paramètres du navigateur et réessayez.";
4489
+ }
4490
+ if (errorName === 'NotFoundError' || errorName === 'DevicesNotFoundError') {
4491
+ return isEn
4492
+ ? 'No microphone found. Please connect a microphone and try again.'
4493
+ : 'Aucun microphone trouvé. Connectez un microphone et réessayez.';
4494
+ }
4495
+ if (errorName === 'NotReadableError' || errorName === 'TrackStartError') {
4496
+ return isEn
4497
+ ? 'Microphone is busy or unavailable. Close other apps using the microphone and try again.'
4498
+ : 'Le microphone est occupé ou indisponible. Fermez les autres applications utilisant le microphone et réessayez.';
4499
+ }
4500
+ if (errorName === 'AbortError') {
4501
+ return isEn
4502
+ ? 'Microphone access was interrupted. Please try again.'
4503
+ : "L'accès au microphone a été interrompu. Veuillez réessayer.";
4504
+ }
4505
+ if (errorName === 'OverconstrainedError') {
4506
+ return isEn
4507
+ ? 'Microphone does not support the required audio settings.'
4508
+ : 'Le microphone ne supporte pas les paramètres audio requis.';
4509
+ }
4510
+ 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'))) {
4511
+ return isEn
4512
+ ? 'Microphone access requires HTTPS or localhost.'
4513
+ : "L'accès au microphone nécessite HTTPS ou localhost.";
4514
+ }
4515
+ return isEn ? `Microphone error: ${error.message}` : `Erreur microphone : ${error.message}`;
4516
+ }
4517
+ getReadableErrorMessage(error, phase) {
4518
+ const isEn = this.language === 'en';
4519
+ const msg = error.message || '';
4520
+ if (/401|unauthorized|invalid.{0,20}(api )?key|authentication failed/i.test(msg)) {
4521
+ return isEn
4522
+ ? 'Authentication failed: invalid API key. Check your configuration.'
4523
+ : "Échec d'authentification : clé API invalide. Vérifiez votre configuration.";
4524
+ }
4525
+ if (/403|forbidden/i.test(msg)) {
4526
+ return isEn
4527
+ ? 'Access denied: check your API permissions.'
4528
+ : 'Accès refusé : vérifiez vos permissions API.';
4529
+ }
4530
+ if (/429|rate.{0,10}limit|too many requests/i.test(msg)) {
4531
+ return isEn
4532
+ ? 'Rate limit exceeded. Please wait a moment and try again.'
4533
+ : 'Limite de requêtes dépassée. Veuillez attendre quelques secondes et réessayer.';
4534
+ }
4535
+ if (/404|not found/i.test(msg)) {
4536
+ return isEn
4537
+ ? 'API endpoint not found. Check your proxy URL configuration.'
4538
+ : "Endpoint API introuvable. Vérifiez l'URL de votre proxy.";
4539
+ }
4540
+ if (/5\d\d|server error|internal server|bad gateway|service unavailable/i.test(msg)) {
4541
+ return isEn
4542
+ ? 'Server error. Please try again in a moment.'
4543
+ : 'Erreur serveur. Veuillez réessayer dans un instant.';
4544
+ }
4545
+ if (/network|failed to fetch|err_connection|connection refused/i.test(msg)) {
4546
+ return isEn
4547
+ ? 'Network error: check your connection and proxy server.'
4548
+ : 'Erreur réseau : vérifiez votre connexion et le serveur proxy.';
4549
+ }
4550
+ if (/json|parse error|unexpected token|invalid response/i.test(msg)) {
4551
+ return isEn
4552
+ ? 'Unexpected response from AI service. Please try again.'
4553
+ : 'Réponse inattendue du service IA. Veuillez réessayer.';
4554
+ }
4555
+ if (phase === 'transcription') {
4556
+ return isEn ? `Transcription failed: ${msg}` : `Échec de la transcription : ${msg}`;
4557
+ }
4558
+ if (phase === 'llm') {
4559
+ return isEn ? `Form filling failed: ${msg}` : `Échec du remplissage du formulaire : ${msg}`;
4560
+ }
4561
+ if (phase === 'ocr') {
4562
+ return isEn ? `OCR processing failed: ${msg}` : `Échec du traitement OCR : ${msg}`;
4563
+ }
4564
+ return isEn ? `Processing error: ${msg}` : `Erreur de traitement : ${msg}`;
4565
+ }
4457
4566
  async handleRecordClick() {
4458
4567
  if (this.isProcessing)
4459
4568
  return;
@@ -4467,7 +4576,7 @@ const VoiceFormRecorder = class {
4467
4576
  async startRecording() {
4468
4577
  var _a;
4469
4578
  try {
4470
- this.hasError = false;
4579
+ this.hasTransientError = false;
4471
4580
  this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
4472
4581
  this.updateDebugInfo('Start Recording Attempt', {});
4473
4582
  await this.audioRecorder.startRecording();
@@ -4480,8 +4589,8 @@ const VoiceFormRecorder = class {
4480
4589
  });
4481
4590
  }
4482
4591
  catch (error) {
4483
- this.hasError = true;
4484
- this.statusMessage = (this.language == 'en' ? `Recording failed: ${error.message}` : `Echec de l'enregistrement : ${error.message}`);
4592
+ this.hasTransientError = true;
4593
+ this.statusMessage = this.getMicErrorMessage(error);
4485
4594
  this.updateDebugInfo('Recording Error', { error: error.message });
4486
4595
  this.formFilled.emit({
4487
4596
  success: false,
@@ -4491,40 +4600,38 @@ const VoiceFormRecorder = class {
4491
4600
  }
4492
4601
  async processJsonForm(ocrData) {
4493
4602
  var _a, _b, _c;
4603
+ if (this.isProcessing || this.isRecording)
4604
+ return;
4605
+ this.isProcessing = true;
4606
+ this.hasTransientError = false;
4607
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing document...' : 'Traitement du document ...');
4494
4608
  try {
4495
- this.isProcessing = true;
4496
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing json...' : `Traitement du json ...`);
4497
- // Extract content from OCR format
4498
4609
  const extractedData = (ocrData === null || ocrData === void 0 ? void 0 : ocrData.content) || ocrData;
4499
4610
  const jsonForm = JSON.stringify(extractedData);
4500
- // Fill form using LLM
4611
+ if (!extractedData) {
4612
+ throw Object.assign(new Error('OCR returned no data'), { phase: 'ocr' });
4613
+ }
4501
4614
  this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4502
4615
  const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4503
- const filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
4504
- // Extract filled data
4616
+ let filledSchema;
4617
+ try {
4618
+ filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
4619
+ }
4620
+ catch (llmError) {
4621
+ throw Object.assign(new Error(llmError.message), { phase: 'llm' });
4622
+ }
4505
4623
  this.filledData = this.extractFilledData(filledSchema);
4506
- this.updateDebugInfo('Form Filled', {
4507
- filledSchema,
4508
- extractedData: this.filledData
4509
- });
4624
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
4510
4625
  this.parsedSchema = this.filledData;
4511
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
4512
- this.hasError = false;
4513
- // Emit success event
4514
- this.formFilled.emit({
4515
- success: true,
4516
- data: this.filledData,
4517
- jsonForm: jsonForm
4518
- });
4626
+ this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
4627
+ this.formFilled.emit({ success: true, data: this.filledData, jsonForm });
4519
4628
  }
4520
4629
  catch (error) {
4521
- this.hasError = true;
4522
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
4523
- this.formFilled.emit({
4524
- success: false,
4525
- error: error.message,
4526
- jsonForm: JSON.stringify(ocrData)
4527
- });
4630
+ console.error('OCR processing error:', error);
4631
+ this.hasTransientError = true;
4632
+ this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'llm');
4633
+ this.updateDebugInfo('OCR Processing Error', { phase: error.phase, error: error.message });
4634
+ this.formFilled.emit({ success: false, error: error.message, jsonForm: JSON.stringify(ocrData) });
4528
4635
  }
4529
4636
  finally {
4530
4637
  this.isProcessing = false;
@@ -4532,83 +4639,102 @@ const VoiceFormRecorder = class {
4532
4639
  }
4533
4640
  async processAudioContent(audioFile) {
4534
4641
  var _a, _b, _c;
4535
- this.updateDebugInfo('Audio Captured', {
4536
- size: audioFile.size,
4537
- type: audioFile.type
4538
- });
4642
+ if (this.isProcessing || this.isRecording)
4643
+ return;
4644
+ this.isProcessing = true;
4645
+ this.hasTransientError = false;
4646
+ this.updateDebugInfo('Audio Captured', { size: audioFile.size, type: audioFile.type });
4539
4647
  try {
4540
- // Transcribe audio
4541
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
4542
- const transcription = await this.speechToTextService.transcribe(audioFile, this.language);
4543
- this.transcription = transcription;
4544
- this.updateDebugInfo('Transcription Complete', { transcription });
4648
+ // Validate audio file before sending to API
4649
+ const MAX_AUDIO_SIZE = 25 * 1024 * 1024; // 25MB (API limit)
4650
+ if (audioFile.size <= 44) { // WAV header alone is 44 bytes; effectively empty
4651
+ this.hasTransientError = true;
4652
+ this.statusMessage = this.language == 'en'
4653
+ ? 'Recording is empty or too short. Please try again.'
4654
+ : "L'enregistrement est vide ou trop court. Veuillez réessayer.";
4655
+ this.formFilled.emit({ success: false, error: 'Empty or too short audio' });
4656
+ return;
4657
+ }
4658
+ if (audioFile.size > MAX_AUDIO_SIZE) {
4659
+ this.hasTransientError = true;
4660
+ this.statusMessage = this.language == 'en'
4661
+ ? `File too large (${Math.round(audioFile.size / 1024 / 1024)}MB). Maximum is 25MB.`
4662
+ : `Fichier trop volumineux (${Math.round(audioFile.size / 1024 / 1024)}Mo). Maximum : 25Mo.`;
4663
+ this.formFilled.emit({ success: false, error: 'Audio file too large' });
4664
+ return;
4665
+ }
4666
+ // Step 1: Transcription
4667
+ let transcription;
4668
+ try {
4669
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.transcribing) || (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
4670
+ transcription = await this.speechToTextService.transcribe(audioFile, this.language);
4671
+ this.transcription = transcription;
4672
+ this.updateDebugInfo('Transcription Complete', { transcription });
4673
+ }
4674
+ catch (transcriptionError) {
4675
+ throw Object.assign(new Error(transcriptionError.message), { phase: 'transcription' });
4676
+ }
4545
4677
  if (!transcription.trim()) {
4546
- throw new Error('No speech detected in the recording');
4678
+ this.hasTransientError = true;
4679
+ this.statusMessage = this.language == 'en'
4680
+ ? 'No speech detected. Please speak clearly and try again.'
4681
+ : 'Aucune parole détectée. Parlez clairement et réessayez.';
4682
+ this.formFilled.emit({ success: false, error: 'No speech detected', transcription: '' });
4683
+ return;
4684
+ }
4685
+ // Step 2: LLM form filling
4686
+ let filledSchema;
4687
+ try {
4688
+ this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4689
+ const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4690
+ filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
4691
+ }
4692
+ catch (llmError) {
4693
+ throw Object.assign(new Error(llmError.message), { phase: 'llm' });
4547
4694
  }
4548
- // Fill form using LLM
4549
- this.statusMessage = ((_b = this.parsedTheme.texts) === null || _b === void 0 ? void 0 : _b.filling) || (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
4550
- const trimmedSchema = await this.trimSchemaForAI(this.parsedSchema);
4551
- const filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
4552
- // Extract filled data
4553
4695
  this.filledData = this.extractFilledData(filledSchema);
4554
- this.updateDebugInfo('Form Filled', {
4555
- filledSchema,
4556
- extractedData: this.filledData
4557
- });
4696
+ this.updateDebugInfo('Form Filled', { filledSchema, extractedData: this.filledData });
4558
4697
  this.parsedSchema = this.filledData;
4559
- this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
4560
- this.hasError = false;
4561
- // Emit success event
4562
- this.formFilled.emit({
4563
- success: true,
4564
- data: this.filledData,
4565
- transcription: transcription
4566
- });
4698
+ this.statusMessage = ((_c = this.parsedTheme.texts) === null || _c === void 0 ? void 0 : _c.completed) || (this.language == 'en' ? 'Form completed!' : 'Formulaire rempli !');
4699
+ this.formFilled.emit({ success: true, data: this.filledData, transcription });
4567
4700
  }
4568
4701
  catch (error) {
4569
- console.error('Transcription error:', error);
4570
- this.hasError = true;
4571
- this.statusMessage = (this.language == 'en' ? 'Audio processing failed.' : 'Échec du traitement audio.');
4572
- this.updateDebugInfo('Transcription Error', { error: error.message });
4573
- this.formFilled.emit({
4574
- success: false,
4575
- error: error.message,
4576
- transcription: this.transcription
4577
- });
4702
+ console.error('Audio processing error:', error);
4703
+ this.hasTransientError = true;
4704
+ this.statusMessage = this.getReadableErrorMessage(error, error.phase || 'audio-upload');
4705
+ this.updateDebugInfo('Audio Processing Error', { phase: error.phase, error: error.message });
4706
+ this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
4707
+ }
4708
+ finally {
4709
+ this.isProcessing = false;
4578
4710
  }
4579
4711
  }
4580
4712
  async stopRecordingAndProcess() {
4581
4713
  var _a;
4714
+ this.isRecording = false;
4715
+ this.isProcessing = true;
4716
+ this.hasTransientError = false;
4717
+ this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
4718
+ this.updateDebugInfo('Stop Recording', {});
4719
+ this.recordingStateChanged.emit({ isRecording: false, state: 'processing' });
4582
4720
  try {
4583
- this.isRecording = false;
4584
- this.isProcessing = true;
4585
- this.statusMessage = ((_a = this.parsedTheme.texts) === null || _a === void 0 ? void 0 : _a.processing) || (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
4586
- this.updateDebugInfo('Stop Recording', {});
4587
- this.recordingStateChanged.emit({
4588
- isRecording: false,
4589
- state: 'processing'
4590
- });
4591
- // Stop recording and get audio blob
4592
4721
  const audioBlob = await this.audioRecorder.stopRecording();
4593
4722
  const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
4594
- this.processAudioContent(audioContent);
4723
+ // processAudioContent manages isProcessing itself; await ensures we don't return early
4724
+ await this.processAudioContent(audioContent);
4595
4725
  }
4596
4726
  catch (error) {
4597
- this.hasError = true;
4598
- this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
4599
- this.updateDebugInfo('Processing Error', { error: error.message });
4600
- this.formFilled.emit({
4601
- success: false,
4602
- error: error.message,
4603
- transcription: this.transcription
4604
- });
4727
+ // Only reached if audioRecorder.stopRecording() itself throws
4728
+ this.hasTransientError = true;
4729
+ this.isProcessing = false;
4730
+ this.statusMessage = (this.language == 'en'
4731
+ ? `Failed to stop recording: ${error.message}`
4732
+ : `Impossible d'arrêter l'enregistrement : ${error.message}`);
4733
+ this.updateDebugInfo('Stop Recording Error', { error: error.message });
4734
+ this.formFilled.emit({ success: false, error: error.message, transcription: this.transcription });
4605
4735
  }
4606
4736
  finally {
4607
- this.isProcessing = false;
4608
- this.recordingStateChanged.emit({
4609
- isRecording: false,
4610
- state: 'idle'
4611
- });
4737
+ this.recordingStateChanged.emit({ isRecording: false, state: 'idle' });
4612
4738
  }
4613
4739
  }
4614
4740
  extractFilledData(filledData) {
@@ -4831,7 +4957,7 @@ const VoiceFormRecorder = class {
4831
4957
  textColor: ocrStyle.textColor || '#ffffff'
4832
4958
  }
4833
4959
  };
4834
- const isDisabled = this.isProcessing || this.hasError;
4960
+ const isDisabled = this.isProcessing || this.hasError || this.isRecording;
4835
4961
  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%' } })));
4836
4962
  }
4837
4963
  convertSchemaToOcrFormat(schema) {
@@ -4886,7 +5012,7 @@ const VoiceFormRecorder = class {
4886
5012
  return;
4887
5013
  const audioStyle = ((_a = this.parsedTheme.buttons) === null || _a === void 0 ? void 0 : _a.audio) || {};
4888
5014
  const size = audioStyle.size || '50px';
4889
- const isDisabled = this.isProcessing || this.hasError;
5015
+ const isDisabled = this.isProcessing || this.hasError || this.isRecording;
4890
5016
  const buttonStyle = {
4891
5017
  stroke: audioStyle.backgroundColor || '#3b82f6',
4892
5018
  fill: audioStyle.backgroundColor || '#3b82f6',
@@ -4915,13 +5041,26 @@ const VoiceFormRecorder = class {
4915
5041
  };
4916
5042
  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" })))));
4917
5043
  }
5044
+ renderLoadingSkeleton() {
5045
+ var _a;
5046
+ const types = this.parsedInputTypes.length > 0 ? this.parsedInputTypes : ['voice'];
5047
+ const containerStyle = this.getContainerStyle();
5048
+ const buttons = (((_a = this.parsedTheme) === null || _a === void 0 ? void 0 : _a.buttons) || {});
5049
+ 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'; };
5050
+ 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 => {
5051
+ const size = getSize(type);
5052
+ return h$1("div", { class: "skeleton-circle", style: { width: size, height: size } });
5053
+ })), this.displayStatus && h$1("div", { class: "skeleton-status" }))));
5054
+ }
4918
5055
  renderStatusMessage() {
5056
+ const hasAnyError = this.hasError || this.hasTransientError;
4919
5057
  const statusClass = [
4920
5058
  'status-text',
4921
- this.hasError && 'error',
4922
- this.filledData && !this.hasError && 'success'
5059
+ hasAnyError && 'error',
5060
+ this.filledData && !hasAnyError && 'success'
4923
5061
  ].filter(Boolean).join(' ');
4924
- return h$1("div", { class: statusClass }, this.statusMessage);
5062
+ const statusStyle = this.getStatusStyle();
5063
+ return h$1("div", { class: statusClass, style: statusStyle }, this.statusMessage);
4925
5064
  }
4926
5065
  renderFormPreview() {
4927
5066
  if (!this.parsedSchema)
@@ -5093,9 +5232,11 @@ const VoiceFormRecorder = class {
5093
5232
  };
5094
5233
  }
5095
5234
  render() {
5235
+ if (this.isInitializing) {
5236
+ return this.renderLoadingSkeleton();
5237
+ }
5096
5238
  const containerStyle = this.getContainerStyle();
5097
- const statusStyle = this.getStatusStyle();
5098
- 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() : "")));
5239
+ 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() : "")));
5099
5240
  }
5100
5241
  static get watchers() { return {
5101
5242
  "formJson": ["initializeServices"],