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