reneco-advanced-input-module 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/voice-input-module.cjs.entry.js +281 -65
  3. package/dist/cjs/voice-input-module.cjs.entry.js.map +1 -1
  4. package/dist/cjs/voice-input-module.cjs.js +1 -1
  5. package/dist/cjs/voice-input-module.entry.cjs.js.map +1 -1
  6. package/dist/collection/components/voice-input-module/voice-input-module.js +91 -7
  7. package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -1
  8. package/dist/collection/services/audio-recorder.service.js +61 -44
  9. package/dist/collection/services/audio-recorder.service.js.map +1 -1
  10. package/dist/collection/services/llm.service.js +140 -8
  11. package/dist/collection/services/llm.service.js.map +1 -1
  12. package/dist/collection/services/speech-to-text.service.js +39 -5
  13. package/dist/collection/services/speech-to-text.service.js.map +1 -1
  14. package/dist/collection/types/service-providers.types.js +9 -1
  15. package/dist/collection/types/service-providers.types.js.map +1 -1
  16. package/dist/components/voice-input-module.js +288 -66
  17. package/dist/components/voice-input-module.js.map +1 -1
  18. package/dist/esm/loader.js +1 -1
  19. package/dist/esm/voice-input-module.entry.js +281 -65
  20. package/dist/esm/voice-input-module.entry.js.map +1 -1
  21. package/dist/esm/voice-input-module.js +1 -1
  22. package/dist/types/components/voice-input-module/voice-input-module.d.ts +3 -0
  23. package/dist/types/components.d.ts +18 -0
  24. package/dist/types/services/audio-recorder.service.d.ts +5 -0
  25. package/dist/types/services/llm.service.d.ts +22 -0
  26. package/dist/types/services/speech-to-text.service.d.ts +8 -0
  27. package/dist/types/types/service-providers.types.d.ts +6 -2
  28. package/dist/voice-input-module/p-0e2b9ca0.entry.js +3 -0
  29. package/dist/voice-input-module/p-0e2b9ca0.entry.js.map +1 -0
  30. package/dist/voice-input-module/voice-input-module.entry.esm.js.map +1 -1
  31. package/dist/voice-input-module/voice-input-module.esm.js +1 -1
  32. package/package.json +1 -1
  33. package/readme.md +81 -0
  34. package/www/build/p-0e2b9ca0.entry.js +3 -0
  35. package/www/build/p-0e2b9ca0.entry.js.map +1 -0
  36. package/www/build/p-812b92c7.js +2 -0
  37. package/www/build/voice-input-module.entry.esm.js.map +1 -1
  38. package/www/build/voice-input-module.esm.js +1 -1
  39. package/www/index.html +12 -1
  40. package/dist/voice-input-module/p-b1207e8d.entry.js +0 -3
  41. package/dist/voice-input-module/p-b1207e8d.entry.js.map +0 -1
  42. package/www/build/p-5880afd7.js +0 -2
  43. package/www/build/p-b1207e8d.entry.js +0 -3
  44. package/www/build/p-b1207e8d.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"],"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"]}]]]], 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],"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
9
  };
10
10
 
11
11
  export { defineCustomElements };
@@ -1,68 +1,95 @@
1
1
  import { r as registerInstance, c as createEvent, h as h$1 } from './index-jmc2yzBp.js';
2
2
 
3
+ const TRANSCRIPTION_MODELS = {
4
+ openai: ['gpt-4o-transcribe', 'gpt-4o-mini-transcribe', 'whisper-1'],
5
+ mistral: ['voxtral-mini-latest', 'voxtral-mini-transcribe-realtime-latest'],
6
+ };
7
+ const COMPLETION_MODELS = {
8
+ openai: ['gpt-5', 'gpt-5-mini', 'gpt-5.5', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'gpt-5.2', 'gpt-5.3', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-pro', 'gpt-5.4-nano'],
9
+ anthropic: ['claude-opus-4', 'claude-sonnet-4', 'claude-haiku-4', 'claude-3.7-sonnet', 'claude-3.5-sonnet', 'claude-opus-4.7', 'claude-opus-4.6', 'claude-opus-4.5', 'claude-sonnet-4.6', 'claude-sonnet-4.5'],
10
+ mistral: ['mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', 'ministral', 'mistral-nemo'],
11
+ };
12
+
3
13
  class AudioRecorderService {
4
14
  constructor() {
5
15
  this.mediaRecorder = null;
6
16
  this.audioChunks = [];
7
17
  this.stream = null;
18
+ this.audioContext = null;
19
+ this.scriptProcessor = null;
20
+ this.pcmChunks = [];
21
+ this.sampleRate = 16000;
8
22
  }
9
23
  async startRecording() {
10
- try {
11
- // Check if the API exists before calling
12
- if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
13
- console.error('Failed to start recording:', 'Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');
14
- return; // Exit gracefully instead of throwing
15
- }
16
- this.stream = await navigator.mediaDevices.getUserMedia({
17
- audio: {
18
- echoCancellation: true,
19
- noiseSuppression: true,
20
- autoGainControl: true
21
- }
22
- });
23
- this.audioChunks = [];
24
- this.mediaRecorder = new MediaRecorder(this.stream, {
25
- mimeType: 'audio/webm;codecs=opus'
26
- });
27
- this.mediaRecorder.ondataavailable = (event) => {
28
- if (event.data && event.data.size > 0) {
29
- this.audioChunks.push(event.data);
30
- }
31
- };
32
- this.mediaRecorder.start(100); // Collect data every 100ms
33
- }
34
- catch (error) {
35
- console.error('Failed to start recording:', error);
24
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
25
+ throw new Error('Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');
36
26
  }
27
+ this.stream = await navigator.mediaDevices.getUserMedia({
28
+ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }
29
+ });
30
+ this.audioContext = new AudioContext({ sampleRate: this.sampleRate });
31
+ const source = this.audioContext.createMediaStreamSource(this.stream);
32
+ this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);
33
+ this.pcmChunks = [];
34
+ this.scriptProcessor.onaudioprocess = (e) => {
35
+ const input = e.inputBuffer.getChannelData(0);
36
+ this.pcmChunks.push(new Float32Array(input));
37
+ };
38
+ source.connect(this.scriptProcessor);
39
+ this.scriptProcessor.connect(this.audioContext.destination);
37
40
  }
38
41
  async stopRecording() {
39
- return new Promise((resolve, reject) => {
40
- if (!this.mediaRecorder) {
41
- reject(new Error('No active recording found'));
42
- return;
42
+ if (!this.audioContext || !this.scriptProcessor) {
43
+ throw new Error('No active recording found');
44
+ }
45
+ this.scriptProcessor.disconnect();
46
+ await this.audioContext.close();
47
+ const wavBlob = this.encodeWav(this.pcmChunks, this.sampleRate);
48
+ this.cleanup();
49
+ return wavBlob;
50
+ }
51
+ encodeWav(chunks, sampleRate) {
52
+ const totalSamples = chunks.reduce((acc, c) => acc + c.length, 0);
53
+ const buffer = new ArrayBuffer(44 + totalSamples * 2);
54
+ const view = new DataView(buffer);
55
+ const writeStr = (offset, str) => {
56
+ for (let i = 0; i < str.length; i++)
57
+ view.setUint8(offset + i, str.charCodeAt(i));
58
+ };
59
+ writeStr(0, 'RIFF');
60
+ view.setUint32(4, 36 + totalSamples * 2, true);
61
+ writeStr(8, 'WAVE');
62
+ writeStr(12, 'fmt ');
63
+ view.setUint32(16, 16, true); // PCM chunk size
64
+ view.setUint16(20, 1, true); // PCM format
65
+ view.setUint16(22, 1, true); // mono
66
+ view.setUint32(24, sampleRate, true);
67
+ view.setUint32(28, sampleRate * 2, true); // byte rate
68
+ view.setUint16(32, 2, true); // block align
69
+ view.setUint16(34, 16, true); // bits per sample
70
+ writeStr(36, 'data');
71
+ view.setUint32(40, totalSamples * 2, true);
72
+ let offset = 44;
73
+ for (const chunk of chunks) {
74
+ for (let i = 0; i < chunk.length; i++) {
75
+ const s = Math.max(-1, Math.min(1, chunk[i]));
76
+ view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
77
+ offset += 2;
43
78
  }
44
- this.mediaRecorder.onstop = () => {
45
- const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
46
- this.cleanup();
47
- resolve(audioBlob);
48
- };
49
- this.mediaRecorder.onerror = (event) => {
50
- reject(new Error(`Recording error: ${event}`));
51
- };
52
- this.mediaRecorder.stop();
53
- });
79
+ }
80
+ return new Blob([buffer], { type: 'audio/wav' });
54
81
  }
55
82
  isRecording() {
56
- var _a;
57
- return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
83
+ return this.scriptProcessor !== null;
58
84
  }
59
85
  cleanup() {
60
86
  if (this.stream) {
61
87
  this.stream.getTracks().forEach(track => track.stop());
62
88
  this.stream = null;
63
89
  }
64
- this.mediaRecorder = null;
65
- this.audioChunks = [];
90
+ this.audioContext = null;
91
+ this.scriptProcessor = null;
92
+ this.pcmChunks = [];
66
93
  }
67
94
  }
68
95
 
@@ -124,15 +151,49 @@ class WhisperSpeechToTextService {
124
151
  }
125
152
  }
126
153
  }
154
+ class MistralSpeechToTextService {
155
+ constructor(config) {
156
+ this.useProxy = (config === null || config === void 0 ? void 0 : config.useProxy) || false;
157
+ this.proxyUrl = (config === null || config === void 0 ? void 0 : config.proxyUrl) || 'http://localhost:8492';
158
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'voxtral-mini-latest';
159
+ this.apiKey = this.useProxy ? '' : ((config === null || config === void 0 ? void 0 : config.apiKey) || '');
160
+ if (!this.useProxy && !this.apiKey) {
161
+ throw new Error('Mistral API key is required for speech-to-text service');
162
+ }
163
+ }
164
+ async transcribe(audioContent, lang = 'en') {
165
+ var _a;
166
+ try {
167
+ const formData = new FormData();
168
+ formData.append('file', audioContent);
169
+ formData.append('model', this.model);
170
+ formData.append('language', lang);
171
+ const endpoint = this.useProxy ? `${this.proxyUrl}/transcribe-mistral` : 'https://api.mistral.ai/v1/audio/transcriptions';
172
+ const headers = {};
173
+ if (!this.useProxy) {
174
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
175
+ }
176
+ const response = await fetch(endpoint, { method: 'POST', headers, body: formData });
177
+ if (!response.ok) {
178
+ const err = await response.json().catch(() => ({ error: 'Unknown error' }));
179
+ throw new Error(`Transcription failed: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
180
+ }
181
+ const result = await response.json();
182
+ return result.text || '';
183
+ }
184
+ catch (error) {
185
+ throw new Error(`Mistral speech-to-text failed: ${error.message}`);
186
+ }
187
+ }
188
+ }
127
189
  class SpeechToTextServiceFactory {
128
190
  static create(config) {
129
191
  var _a;
130
- const provider = ((_a = config.speechToText) === null || _a === void 0 ? void 0 : _a.provider) || 'whisper';
192
+ const provider = ((_a = config.speechToText) === null || _a === void 0 ? void 0 : _a.provider) || 'openai';
131
193
  switch (provider) {
132
- case 'whisper':
133
- return new WhisperSpeechToTextService(config.speechToText);
134
- default:
135
- throw new Error(`Unsupported speech-to-text provider: ${provider}`);
194
+ case 'openai': return new WhisperSpeechToTextService(config.speechToText);
195
+ case 'mistral': return new MistralSpeechToTextService(config.speechToText);
196
+ default: throw new Error(`Unsupported speech-to-text provider: ${provider}`);
136
197
  }
137
198
  }
138
199
  }
@@ -322,8 +383,10 @@ class OpenAILLMService {
322
383
  `(${field.type}` +
323
384
  `${field.required ? ', required' : ''}` +
324
385
  `${field.readonly ? ', readonly' : ''}` +
386
+ `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default=' + field.default : ''}` +
325
387
  `${field.min && field.min !== "" ? ', min=' + field.min : ''}` +
326
- `${field.max && field.max !== "" ? ', max=' + field.max : ''}`;
388
+ `${field.max && field.max !== "" ? ', max=' + field.max : ''}` +
389
+ `${field.pattern && field.pattern !== "" ? ', pattern=' + field.pattern : ''}`;
327
390
  if (field.options && field.options.length > 0) {
328
391
  if (field.options.length < 50) {
329
392
  description += `) - options: ${field.options.join(', ')}`;
@@ -343,8 +406,10 @@ class OpenAILLMService {
343
406
  `(${field.type}` +
344
407
  `${field.required ? ', required' : ''}` +
345
408
  `${field.readonly ? ', readonly' : ''}` +
409
+ `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default=' + field.default : ''}` +
346
410
  `${field.min && field.min !== "" ? ', min=' + field.min : ''}` +
347
- `${field.max && field.max !== "" ? ', max=' + field.max : ''}`;
411
+ `${field.max && field.max !== "" ? ', max=' + field.max : ''}` +
412
+ `${field.pattern && field.pattern !== "" ? ', pattern=' + field.pattern : ''}`;
348
413
  if (field.options && field.options.length > 0) {
349
414
  if (field.options.length < 50) {
350
415
  description += `) - options: ${field.options.join(', ')}`;
@@ -488,11 +553,14 @@ class OpenAILLMService {
488
553
  7. Only include fields where relevant information is found
489
554
  8. The current GMT datetime is ${new Date().toGMTString()}
490
555
  9. Respect the constraints written between parenthesis for readonly status (readonly fields MUST NOT be filled with values), min and max values, whatever the transcription says
556
+ 9b. CRITICAL - DATE/DATETIME OUT OF RANGE: For date and datetime fields with min and/or max constraints, if the value from the transcription falls outside the allowed range, leave the field EMPTY. NEVER clamp, adjust, or substitute a boundary date - either the date is valid and within range, or the field is left empty
491
557
  10. IMPORTANT: Fields marked as "readonly" are presentation elements (headers, labels) that provide context but MUST NOT receive values
492
558
  11. CRITICAL - DUPLICATE FIELDS: When you see multiple fields with the same name but different IDs (e.g., "ID:field1 | Event date" and "ID:field2 | Event date"), these are DIFFERENT fields in DIFFERENT sections. The user may explicitly say which section they are filling (e.g., "for the first/second sub-form"). Listen VERY CAREFULLY to these contextual clues. Use the readonly headers to understand which section each field belongs to. If the user says "for the clinical sign, the event date is X", you must fill the Event date field that comes AFTER the "MC Clinical sign" header, NOT the first Event date you see
493
559
  12. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them
494
- 13. CRITICAL: For select fields, options like "No", "Non", "Non applicable", "Inconnu", "Unknown", "N/A" are VALID VALUES that can be selected. When the user says these words, treat them as legitimate option choices, NOT always as negations or refusals to answer. HOWEVER, for non-select fields (string, number, date, etc.), if the user says "I don't know", "unknown", "not known", "inconnu", "je ne sais pas", this usually means they have NO VALUE to provide, unless explicitely precised - leave the field empty, do NOT fill it with the literal text "unknown" or "I don't know"
560
+ 13. CRITICAL - UNKNOWN/NO/N-A VALUES: The interpretation of words like "Unknown", "Inconnu", "No", "Non", "N/A", "Non applicable" depends ENTIRELY on whether the field has those words as explicit options. Rule: if the user says "field X is Unknown" (or any similar phrasing) AND "Unknown" (or a close variant) exists in the options list for that field, then SELECT that option - it is a valid value. If the user says such words for a field that does NOT have them as options (string, number, date, etc.), then it means the user has no value to provide - leave the field empty, do NOT fill it with the literal text
495
561
  14. CRITICAL: Each field has a unique ID shown as "ID:xxx" at the start of its description. You MUST include this exact ID in your response for each field you fill. The ID is the ONLY way to distinguish between fields with the same name
562
+ 15. CRITICAL - DEFAULT VALUES: Some fields have default values shown as "default=xxx". If the user does NOT mention a specific value for that field in the transcription, you MUST return the default value. Default values should be preserved unless explicitly overridden by the user
563
+ 16. CRITICAL - PATTERN VALIDATION: Some fields have a regex pattern shown as "pattern=xxx". The value you extract MUST match this pattern exactly. If the transcription is too ambiguous to produce a value that matches the pattern, leave the field EMPTY
496
564
 
497
565
  Respond with JSON in this exact format: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
498
566
  let userPrompt = `
@@ -561,10 +629,12 @@ class OpenAILLMService {
561
629
  8. Return the same schema structure with 'default' values filled
562
630
  9. The current GMT datetime is ${new Date().toGMTString()}
563
631
  10. Respect the constraints written between parenthesis for readonly status (readonly fields MUST NOT be filled with values), min and max values, whatever the transcription says
632
+ 10b. CRITICAL - DATE/DATETIME OUT OF RANGE: For date and datetime fields with min and/or max constraints, if the value from the transcription falls outside the allowed range, leave the field EMPTY. NEVER clamp, adjust, or substitute a boundary date - either the date is valid and within range, or the field is left empty
564
633
  11. IMPORTANT: Fields marked as "readonly" are presentation elements (headers, labels) that provide context but MUST NOT receive values
565
634
  12. CRITICAL - DUPLICATE FIELDS: When you see multiple fields with the same name but different IDs (e.g., "ID:field1 | Event date" and "ID:field2 | Event date"), these are DIFFERENT fields in DIFFERENT sections. The user will explicitly say which section they are filling (e.g., "for the first/second sub-form", "in the anamnesis section", "for the clinical sign"). Listen VERY CAREFULLY to these contextual clues. Use the readonly headers to understand which section each field belongs to. If the user says "for the clinical sign, the event date is X", you must fill the Event date field that comes AFTER the "MC Clinical sign" header, NOT the first Event date you see
566
635
  13. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them
567
- 14. CRITICAL: For select fields, options like "No", "Non", "Non applicable", "Inconnu", "Unknown", "N/A" are VALID VALUES that can be selected. When the user says these words, treat them as legitimate option choices, NOT always as negations or refusals to answer. HOWEVER, for non-select fields (string, number, date, etc.), if the user says "I don't know", "unknown", "not known", "inconnu", "je ne sais pas", this usually means they have NO VALUE to provide, unless explicitely precised - leave the field empty, do NOT fill it with the literal text "unknown" or "I don't know"
636
+ 14. CRITICAL - UNKNOWN/NO/N-A VALUES: The interpretation of words like "Unknown", "Inconnu", "No", "Non", "N/A", "Non applicable" depends ENTIRELY on whether the field has those words as explicit options. Rule: if the user says "field X is Unknown" (or any similar phrasing) AND "Unknown" (or a close variant) exists in the options list for that field, then SELECT that option - it is a valid value. If the user says such words for a field that does NOT have them as options (string, number, date, etc.), then it means the user has no value to provide - leave the field empty, do NOT fill it with the literal text
637
+ 15. CRITICAL - PATTERN VALIDATION: Some fields have a regex pattern shown as "pattern=xxx". The value you extract MUST match this pattern exactly. If the transcription is too ambiguous to produce a value that matches the pattern, leave the field EMPTY
568
638
 
569
639
  Respond with JSON in this exact format: {"schema": {...}}`;
570
640
  let userPrompt = `
@@ -628,15 +698,138 @@ class OpenAILLMService {
628
698
  }
629
699
  }
630
700
  }
701
+ class AnthropicLLMService {
702
+ constructor(config) {
703
+ this.useProxy = (config === null || config === void 0 ? void 0 : config.useProxy) || false;
704
+ this.proxyUrl = (config === null || config === void 0 ? void 0 : config.proxyUrl) || 'http://localhost:8492';
705
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'claude-sonnet-4.5';
706
+ this.apiKey = this.useProxy ? '' : ((config === null || config === void 0 ? void 0 : config.apiKey) || '');
707
+ if (!this.useProxy && !this.apiKey) {
708
+ throw new Error('Anthropic API key is required');
709
+ }
710
+ }
711
+ async fillFormFromTranscription(transcription, schema) {
712
+ return this.fillForm(transcription, schema);
713
+ }
714
+ async fillFormFromJson(json, schema) {
715
+ return this.fillForm(json, schema);
716
+ }
717
+ async fillForm(data, schema) {
718
+ var _a;
719
+ const endpoint = this.useProxy ? `${this.proxyUrl}/complete-anthropic` : 'https://api.anthropic.com/v1/messages';
720
+ const headers = { 'Content-Type': 'application/json' };
721
+ if (!this.useProxy) {
722
+ headers['x-api-key'] = this.apiKey;
723
+ headers['anthropic-version'] = '2023-06-01';
724
+ }
725
+ const finalSchema = (schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema) || schema;
726
+ const systemPrompt = this.buildSystemPrompt();
727
+ const userPrompt = `Data: "${data}"
728
+
729
+ Form fields:
730
+ ${JSON.stringify(finalSchema, null, 2)}
731
+
732
+ Respond with JSON: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
733
+ const body = this.useProxy
734
+ ? { model: this.model, messages: [{ role: 'user', content: userPrompt }], system: systemPrompt }
735
+ : { model: this.model, max_tokens: 4096, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] };
736
+ const response = await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });
737
+ if (!response.ok) {
738
+ const err = await response.json().catch(() => ({ error: 'Unknown error' }));
739
+ throw new Error(`Anthropic API failed: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
740
+ }
741
+ const result = await response.json();
742
+ const content = this.useProxy ? result.choices[0].message.content : result.content[0].text;
743
+ return JSON.parse(content);
744
+ }
745
+ buildSystemPrompt() {
746
+ return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.
747
+ Rules:
748
+ 1. Only extract values that can be confidently determined
749
+ 2. Respect field types (string, number, datetime, boolean, select)
750
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
751
+ 4. For date fields, use DD/MM/YYYY format
752
+ 5. For select fields, use exact option values from the provided choices
753
+ 6. Leave fields empty if no relevant information is found
754
+ 7. Fields marked readonly MUST NOT receive values
755
+ 8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY
756
+ 9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty
757
+ 10. UNKNOWN VALUES: if the user says "Unknown" and it exists as an option, select it; otherwise leave empty
758
+ Respond ONLY with valid JSON.`;
759
+ }
760
+ }
761
+ class MistralLLMService {
762
+ constructor(config) {
763
+ this.useProxy = (config === null || config === void 0 ? void 0 : config.useProxy) || false;
764
+ this.proxyUrl = (config === null || config === void 0 ? void 0 : config.proxyUrl) || 'http://localhost:8492';
765
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'mistral-medium-latest';
766
+ this.apiKey = this.useProxy ? '' : ((config === null || config === void 0 ? void 0 : config.apiKey) || '');
767
+ if (!this.useProxy && !this.apiKey) {
768
+ throw new Error('Mistral API key is required');
769
+ }
770
+ }
771
+ async fillFormFromTranscription(transcription, schema) {
772
+ return this.fillForm(transcription, schema);
773
+ }
774
+ async fillFormFromJson(json, schema) {
775
+ return this.fillForm(json, schema);
776
+ }
777
+ async fillForm(data, schema) {
778
+ var _a;
779
+ const endpoint = this.useProxy ? `${this.proxyUrl}/complete-mistral` : 'https://api.mistral.ai/v1/chat/completions';
780
+ const headers = { 'Content-Type': 'application/json' };
781
+ if (!this.useProxy) {
782
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
783
+ }
784
+ const finalSchema = (schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema) || schema;
785
+ const systemPrompt = this.buildSystemPrompt();
786
+ const userPrompt = `Data: "${data}"
787
+
788
+ Form fields:
789
+ ${JSON.stringify(finalSchema, null, 2)}
790
+
791
+ Respond with JSON: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
792
+ const response = await fetch(endpoint, {
793
+ method: 'POST',
794
+ headers,
795
+ body: JSON.stringify({
796
+ model: this.model,
797
+ messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }],
798
+ response_format: { type: 'json_object' },
799
+ }),
800
+ });
801
+ if (!response.ok) {
802
+ const err = await response.json().catch(() => ({ error: 'Unknown error' }));
803
+ throw new Error(`Mistral API failed: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
804
+ }
805
+ const result = await response.json();
806
+ return JSON.parse(result.choices[0].message.content);
807
+ }
808
+ buildSystemPrompt() {
809
+ return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.
810
+ Rules:
811
+ 1. Only extract values that can be confidently determined
812
+ 2. Respect field types (string, number, datetime, boolean, select)
813
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
814
+ 4. For date fields, use DD/MM/YYYY format
815
+ 5. For select fields, use exact option values from the provided choices
816
+ 6. Leave fields empty if no relevant information is found
817
+ 7. Fields marked readonly MUST NOT receive values
818
+ 8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY
819
+ 9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty
820
+ 10. UNKNOWN VALUES: if the user says "Unknown" and it exists as an option, select it; otherwise leave empty
821
+ Respond ONLY with valid JSON.`;
822
+ }
823
+ }
631
824
  class LLMServiceFactory {
632
825
  static create(config) {
633
826
  var _a;
634
827
  const provider = ((_a = config.llm) === null || _a === void 0 ? void 0 : _a.provider) || 'openai';
635
828
  switch (provider) {
636
- case 'openai':
637
- return new OpenAILLMService(config.llm);
638
- default:
639
- throw new Error(`Unsupported LLM provider: ${provider}`);
829
+ case 'openai': return new OpenAILLMService(config.llm);
830
+ case 'anthropic': return new AnthropicLLMService(config.llm);
831
+ case 'mistral': return new MistralLLMService(config.llm);
832
+ default: throw new Error(`Unsupported LLM provider: ${provider}`);
640
833
  }
641
834
  }
642
835
  }
@@ -4156,6 +4349,8 @@ const VoiceFormRecorder = class {
4156
4349
  this.apiProxyUrl = 'http://localhost:8492';
4157
4350
  this.transcriptionModel = 'gpt-4o-transcribe';
4158
4351
  this.completionModel = 'gpt-5-mini';
4352
+ this.transcriptionProvider = 'openai';
4353
+ this.completionProvider = 'openai';
4159
4354
  this.context = undefined;
4160
4355
  this.classificationRootUrl = 'http://localhost';
4161
4356
  this.language = 'en';
@@ -4203,17 +4398,33 @@ const VoiceFormRecorder = class {
4203
4398
  this.updateDebugInfo(errorMessage, { error: errorMessage });
4204
4399
  }
4205
4400
  else {
4401
+ // Validate transcription provider/model
4402
+ const allowedTranscriptionModels = TRANSCRIPTION_MODELS[this.transcriptionProvider];
4403
+ if (!allowedTranscriptionModels) {
4404
+ throw new Error(`Unsupported transcription provider: '${this.transcriptionProvider}'. Allowed: openai, mistral`);
4405
+ }
4406
+ if (!allowedTranscriptionModels.includes(this.transcriptionModel)) {
4407
+ throw new Error(`Model '${this.transcriptionModel}' is not allowed for transcription provider '${this.transcriptionProvider}'. Allowed: ${allowedTranscriptionModels.join(', ')}`);
4408
+ }
4409
+ // Validate completion provider/model
4410
+ const allowedCompletionModels = COMPLETION_MODELS[this.completionProvider];
4411
+ if (!allowedCompletionModels) {
4412
+ throw new Error(`Unsupported completion provider: '${this.completionProvider}'. Allowed: openai, anthropic, mistral`);
4413
+ }
4414
+ if (!allowedCompletionModels.includes(this.completionModel)) {
4415
+ throw new Error(`Model '${this.completionModel}' is not allowed for completion provider '${this.completionProvider}'. Allowed: ${allowedCompletionModels.join(', ')}`);
4416
+ }
4206
4417
  // Parse form schema
4207
4418
  this.parsedSchema = JSON.parse(this.formJson || '{}');
4208
4419
  // Parse service configuration
4209
4420
  this.parsedConfig = JSON.parse(this.serviceConfig || '{}');
4210
4421
  // Add API key to config if provided via prop
4211
4422
  if (this.apiKey) {
4212
- this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { apiKey: this.apiKey, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { apiKey: this.apiKey, model: this.completionModel }) });
4423
+ this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { provider: this.transcriptionProvider, apiKey: this.apiKey, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { provider: this.completionProvider, apiKey: this.apiKey, model: this.completionModel }) });
4213
4424
  }
4214
4425
  else {
4215
4426
  // Use proxy API if no API key provided
4216
- this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { useProxy: true, proxyUrl: this.apiProxyUrl, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { useProxy: true, proxyUrl: this.apiProxyUrl, model: this.completionModel }) });
4427
+ this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { provider: this.transcriptionProvider, useProxy: true, proxyUrl: this.apiProxyUrl, model: this.transcriptionModel }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { provider: this.completionProvider, useProxy: true, proxyUrl: this.apiProxyUrl, model: this.completionModel }) });
4217
4428
  }
4218
4429
  // Initialize services
4219
4430
  this.speechToTextService = SpeechToTextServiceFactory.create(this.parsedConfig);
@@ -4379,7 +4590,7 @@ const VoiceFormRecorder = class {
4379
4590
  });
4380
4591
  // Stop recording and get audio blob
4381
4592
  const audioBlob = await this.audioRecorder.stopRecording();
4382
- const audioContent = new File([audioBlob], 'audio.webm', { type: 'audio/webm' });
4593
+ const audioContent = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
4383
4594
  this.processAudioContent(audioContent);
4384
4595
  }
4385
4596
  catch (error) {
@@ -4473,12 +4684,13 @@ const VoiceFormRecorder = class {
4473
4684
  case "ng":
4474
4685
  const trimmed = { fields: [] };
4475
4686
  schema.Children.forEach((child) => {
4476
- var _a;
4687
+ var _a, _b;
4477
4688
  if (!child.System_Name || !child.Type)
4478
4689
  return;
4479
4690
  const fieldData = {
4480
4691
  name: child.Label || ((_a = child.Settings) === null || _a === void 0 ? void 0 : _a.Label) || child.System_Name,
4481
- type: this.mapFieldType(child.Type)
4692
+ type: this.mapFieldType(child.Type),
4693
+ default: (_b = child.Settings) === null || _b === void 0 ? void 0 : _b.Default_Value
4482
4694
  };
4483
4695
  // Add options for classification/select/multiselect fields
4484
4696
  const selectTypes = ['InputClassification', 'select', 'multiselect', 'InputMultiSelect'];
@@ -4554,7 +4766,7 @@ const VoiceFormRecorder = class {
4554
4766
  title: field.title,
4555
4767
  options: field.options,
4556
4768
  readonly: field.type === 'header' || field.Enabled === false,
4557
- default: field.DefaultValue,
4769
+ default: field.default || field.DefaultValue,
4558
4770
  pattern: field.Mask,
4559
4771
  min: field.ValidationMin,
4560
4772
  max: field.ValidationMax
@@ -4883,12 +5095,16 @@ const VoiceFormRecorder = class {
4883
5095
  render() {
4884
5096
  const containerStyle = this.getContainerStyle();
4885
5097
  const statusStyle = this.getStatusStyle();
4886
- return (h$1("div", { key: '4f71f7f6dc15cb9be9545060fe992b4076f592e6' }, h$1("div", { key: '84b0fbc8e896b2b695bf32cd937763621c7fa62a', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : ""), style: containerStyle }, h$1("div", { key: 'd84473027ffd89b30ade93eaa9d8e69c532549a3', 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() : "")));
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() : "")));
4887
5099
  }
4888
5100
  static get watchers() { return {
4889
5101
  "formJson": ["initializeServices"],
4890
5102
  "serviceConfig": ["initializeServices"],
4891
- "theme": ["initializeServices"]
5103
+ "theme": ["initializeServices"],
5104
+ "transcriptionProvider": ["initializeServices"],
5105
+ "completionProvider": ["initializeServices"],
5106
+ "transcriptionModel": ["initializeServices"],
5107
+ "completionModel": ["initializeServices"]
4892
5108
  }; }
4893
5109
  };
4894
5110
  VoiceFormRecorder.style = voiceInputModuleCss;