reneco-advanced-input-module 0.0.1

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 (108) hide show
  1. package/.editorconfig +15 -0
  2. package/.prettierrc.json +13 -0
  3. package/LICENSE +21 -0
  4. package/api-key-inject.js +46 -0
  5. package/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
  6. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
  7. package/dist/cjs/file-uploader.voice-input-module.entry.cjs.js.map +1 -0
  8. package/dist/cjs/file-uploader_2.cjs.entry.js +1319 -0
  9. package/dist/cjs/file-uploader_2.cjs.entry.js.map +1 -0
  10. package/dist/cjs/index-BTSzTkSZ.js +1494 -0
  11. package/dist/cjs/index-BTSzTkSZ.js.map +1 -0
  12. package/dist/cjs/index.cjs.js +5 -0
  13. package/dist/cjs/index.cjs.js.map +1 -0
  14. package/dist/cjs/loader.cjs.js +16 -0
  15. package/dist/cjs/loader.cjs.js.map +1 -0
  16. package/dist/cjs/voice-input-module.cjs.js +28 -0
  17. package/dist/cjs/voice-input-module.cjs.js.map +1 -0
  18. package/dist/collection/collection-manifest.json +13 -0
  19. package/dist/collection/components/file-uploader/file-uploader.css +26 -0
  20. package/dist/collection/components/file-uploader/file-uploader.js +130 -0
  21. package/dist/collection/components/file-uploader/file-uploader.js.map +1 -0
  22. package/dist/collection/components/voice-input-module/voice-input-module.css +251 -0
  23. package/dist/collection/components/voice-input-module/voice-input-module.js +875 -0
  24. package/dist/collection/components/voice-input-module/voice-input-module.js.map +1 -0
  25. package/dist/collection/index.js +12 -0
  26. package/dist/collection/index.js.map +1 -0
  27. package/dist/collection/services/audio-recorder.service.js +66 -0
  28. package/dist/collection/services/audio-recorder.service.js.map +1 -0
  29. package/dist/collection/services/llm.service.js +193 -0
  30. package/dist/collection/services/llm.service.js.map +1 -0
  31. package/dist/collection/services/speech-to-text.service.js +62 -0
  32. package/dist/collection/services/speech-to-text.service.js.map +1 -0
  33. package/dist/collection/types/form-schema.types.js +2 -0
  34. package/dist/collection/types/form-schema.types.js.map +1 -0
  35. package/dist/collection/types/service-providers.types.js +2 -0
  36. package/dist/collection/types/service-providers.types.js.map +1 -0
  37. package/dist/collection/utils/schema-converter.js +422 -0
  38. package/dist/collection/utils/schema-converter.js.map +1 -0
  39. package/dist/components/file-uploader.d.ts +11 -0
  40. package/dist/components/file-uploader.js +9 -0
  41. package/dist/components/file-uploader.js.map +1 -0
  42. package/dist/components/file-uploader2.js +98 -0
  43. package/dist/components/file-uploader2.js.map +1 -0
  44. package/dist/components/index.d.ts +33 -0
  45. package/dist/components/index.js +4 -0
  46. package/dist/components/index.js.map +1 -0
  47. package/dist/components/voice-input-module.d.ts +11 -0
  48. package/dist/components/voice-input-module.js +1292 -0
  49. package/dist/components/voice-input-module.js.map +1 -0
  50. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  51. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  52. package/dist/esm/file-uploader.voice-input-module.entry.js.map +1 -0
  53. package/dist/esm/file-uploader_2.entry.js +1316 -0
  54. package/dist/esm/file-uploader_2.entry.js.map +1 -0
  55. package/dist/esm/index-jmc2yzBp.js +1487 -0
  56. package/dist/esm/index-jmc2yzBp.js.map +1 -0
  57. package/dist/esm/index.js +4 -0
  58. package/dist/esm/index.js.map +1 -0
  59. package/dist/esm/loader.js +14 -0
  60. package/dist/esm/loader.js.map +1 -0
  61. package/dist/esm/voice-input-module.js +24 -0
  62. package/dist/esm/voice-input-module.js.map +1 -0
  63. package/dist/index.cjs.js +1 -0
  64. package/dist/index.js +1 -0
  65. package/dist/types/components/file-uploader/file-uploader.d.ts +8 -0
  66. package/dist/types/components/voice-input-module/voice-input-module.d.ts +55 -0
  67. package/dist/types/components.d.ts +158 -0
  68. package/dist/types/index.d.ts +9 -0
  69. package/dist/types/services/audio-recorder.service.d.ts +9 -0
  70. package/dist/types/services/llm.service.d.ts +15 -0
  71. package/dist/types/services/speech-to-text.service.d.ts +11 -0
  72. package/dist/types/stencil-public-runtime.d.ts +1709 -0
  73. package/dist/types/types/form-schema.types.d.ts +70 -0
  74. package/dist/types/types/service-providers.types.d.ts +20 -0
  75. package/dist/types/utils/schema-converter.d.ts +22 -0
  76. package/dist/voice-input-module/file-uploader.voice-input-module.entry.esm.js.map +1 -0
  77. package/dist/voice-input-module/index.esm.js +2 -0
  78. package/dist/voice-input-module/index.esm.js.map +1 -0
  79. package/dist/voice-input-module/loader.esm.js.map +1 -0
  80. package/dist/voice-input-module/p-7b4f33ba.entry.js +2 -0
  81. package/dist/voice-input-module/p-7b4f33ba.entry.js.map +1 -0
  82. package/dist/voice-input-module/p-DQuL1Twl.js +2 -0
  83. package/dist/voice-input-module/p-DQuL1Twl.js.map +1 -0
  84. package/dist/voice-input-module/p-jmc2yzBp.js +3 -0
  85. package/dist/voice-input-module/p-jmc2yzBp.js.map +1 -0
  86. package/dist/voice-input-module/voice-input-module.esm.js +2 -0
  87. package/dist/voice-input-module/voice-input-module.esm.js.map +1 -0
  88. package/env-config.js +4 -0
  89. package/inject-env.js +20 -0
  90. package/package.json +37 -0
  91. package/readme.md +111 -0
  92. package/src/components/file-uploader/file-uploader.css +26 -0
  93. package/src/components/file-uploader/file-uploader.tsx +100 -0
  94. package/src/components/file-uploader/readme.md +31 -0
  95. package/src/components/voice-input-module/readme.md +114 -0
  96. package/src/components/voice-input-module/voice-input-module.css +251 -0
  97. package/src/components/voice-input-module/voice-input-module.tsx +731 -0
  98. package/src/components.d.ts +158 -0
  99. package/src/index.html +663 -0
  100. package/src/index.ts +12 -0
  101. package/src/services/audio-recorder.service.ts +74 -0
  102. package/src/services/llm.service.ts +221 -0
  103. package/src/services/speech-to-text.service.ts +72 -0
  104. package/src/types/form-schema.types.ts +78 -0
  105. package/src/types/service-providers.types.ts +22 -0
  106. package/src/utils/schema-converter.ts +494 -0
  107. package/stencil.config.ts +24 -0
  108. package/tsconfig.json +30 -0
@@ -0,0 +1,1292 @@
1
+ import { proxyCustomElement, HTMLElement, createEvent, h } from '@stencil/core/internal/client';
2
+ import { d as defineCustomElement$2 } from './file-uploader2.js';
3
+
4
+ class AudioRecorderService {
5
+ constructor() {
6
+ this.mediaRecorder = null;
7
+ this.audioChunks = [];
8
+ this.stream = null;
9
+ }
10
+ async startRecording() {
11
+ try {
12
+ // Check if the API exists before calling
13
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
14
+ console.error('Failed to start recording:', 'Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');
15
+ return; // Exit gracefully instead of throwing
16
+ }
17
+ this.stream = await navigator.mediaDevices.getUserMedia({
18
+ audio: {
19
+ echoCancellation: true,
20
+ noiseSuppression: true,
21
+ autoGainControl: true
22
+ }
23
+ });
24
+ this.audioChunks = [];
25
+ this.mediaRecorder = new MediaRecorder(this.stream, {
26
+ mimeType: 'audio/webm;codecs=opus'
27
+ });
28
+ this.mediaRecorder.ondataavailable = (event) => {
29
+ if (event.data && event.data.size > 0) {
30
+ this.audioChunks.push(event.data);
31
+ }
32
+ };
33
+ this.mediaRecorder.start(100); // Collect data every 100ms
34
+ }
35
+ catch (error) {
36
+ console.error('Failed to start recording:', error);
37
+ }
38
+ }
39
+ async stopRecording() {
40
+ return new Promise((resolve, reject) => {
41
+ if (!this.mediaRecorder) {
42
+ reject(new Error('No active recording found'));
43
+ return;
44
+ }
45
+ this.mediaRecorder.onstop = () => {
46
+ const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
47
+ this.cleanup();
48
+ resolve(audioBlob);
49
+ };
50
+ this.mediaRecorder.onerror = (event) => {
51
+ reject(new Error(`Recording error: ${event}`));
52
+ };
53
+ this.mediaRecorder.stop();
54
+ });
55
+ }
56
+ isRecording() {
57
+ var _a;
58
+ return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
59
+ }
60
+ cleanup() {
61
+ if (this.stream) {
62
+ this.stream.getTracks().forEach(track => track.stop());
63
+ this.stream = null;
64
+ }
65
+ this.mediaRecorder = null;
66
+ this.audioChunks = [];
67
+ }
68
+ }
69
+
70
+ class WhisperSpeechToTextService {
71
+ constructor(config) {
72
+ // Get API key from config or environment
73
+ this.apiKey = (config === null || config === void 0 ? void 0 : config.apiKey) || this.getEnvironmentVariable('OPENAI_API_KEY') || '';
74
+ this.baseUrl = (config === null || config === void 0 ? void 0 : config.baseUrl) || 'https://api.openai.com/v1';
75
+ if (!this.apiKey) {
76
+ throw new Error('OpenAI API key is required for Whisper service');
77
+ }
78
+ }
79
+ getEnvironmentVariable(name) {
80
+ // In browser environment, we might get env vars through other means
81
+ if (typeof process !== 'undefined' && process.env) {
82
+ return process.env[name];
83
+ }
84
+ // Check if it's available as a global variable or through other means
85
+ return window[name] || undefined;
86
+ }
87
+ async transcribe(audioBlob, lang = 'en') {
88
+ var _a;
89
+ try {
90
+ const formData = new FormData();
91
+ // Convert webm to a format Whisper can handle
92
+ const audioFile = new File([audioBlob], 'audio.webm', { type: 'audio/webm' });
93
+ formData.append('file', audioFile);
94
+ formData.append('model', 'gpt-4o-transcribe'); // >>> tronque le texte ?
95
+ // formData.append('model', 'gpt-4o-mini-transcribe');// >>> tronque le texte ?
96
+ // formData.append('model', 'whisper-1');
97
+ formData.append('language', lang);
98
+ formData.append('response_format', 'json');
99
+ formData.append('max_output_tokens', '2000');
100
+ const response = await fetch(`${this.baseUrl}/audio/transcriptions`, {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Authorization': `Bearer ${this.apiKey}`,
104
+ },
105
+ body: formData,
106
+ });
107
+ if (!response.ok) {
108
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
109
+ throw new Error(`Transcription failed: ${((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
110
+ }
111
+ const result = await response.json();
112
+ return result.text || '';
113
+ }
114
+ catch (error) {
115
+ throw new Error(`Speech-to-text transcription failed: ${error.message}`);
116
+ }
117
+ }
118
+ }
119
+ class SpeechToTextServiceFactory {
120
+ static create(config) {
121
+ var _a;
122
+ const provider = ((_a = config.speechToText) === null || _a === void 0 ? void 0 : _a.provider) || 'whisper';
123
+ switch (provider) {
124
+ case 'whisper':
125
+ return new WhisperSpeechToTextService(config.speechToText);
126
+ default:
127
+ throw new Error(`Unsupported speech-to-text provider: ${provider}`);
128
+ }
129
+ }
130
+ }
131
+
132
+ class OpenAILLMService {
133
+ constructor(config) {
134
+ // Get API key from config or environment
135
+ this.apiKey = (config === null || config === void 0 ? void 0 : config.apiKey) || this.getEnvironmentVariable('OPENAI_API_KEY') || '';
136
+ // the newest OpenAI model is "gpt-4.1-mini". do not change this unless explicitly requested by the user
137
+ // this.model = config?.model || 'gpt-4.1-mini';
138
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'gpt-4.1';
139
+ this.baseUrl = (config === null || config === void 0 ? void 0 : config.baseUrl) || 'https://api.openai.com/v1';
140
+ if (!this.apiKey) {
141
+ throw new Error('OpenAI API key is required for LLM service');
142
+ }
143
+ }
144
+ getEnvironmentVariable(name) {
145
+ // In browser environment, we might get env vars through other means
146
+ if (typeof process !== 'undefined' && process.env) {
147
+ return process.env[name];
148
+ }
149
+ // Check if it's available as a global variable or through other means
150
+ return window[name] || undefined;
151
+ }
152
+ getOptimizeFieldsDescription(schema) {
153
+ return Object.values(schema).map((field) => {
154
+ var _a;
155
+ return `- ${(_a = field.name) !== null && _a !== void 0 ? _a : field.title} ` +
156
+ `(${field.type}` +
157
+ `${field.required ? ', required' : ''}` +
158
+ `${field.readonly ? ', readonly' : ''}` +
159
+ `${field.min && field.min !== "" ? ', min=' + field.min : ''}` +
160
+ `${field.max && field.max !== "" ? ', max=' + field.max : ''}` +
161
+ `)` +
162
+ `${field.options ? ` - options: ${field.options.join(', ')}` : ''}`;
163
+ }).join('\n');
164
+ }
165
+ async fillFormFromTranscription(transcription, schema) {
166
+ return this.fillForm(transcription, schema, true);
167
+ }
168
+ async fillFormFromJson(json, schema) {
169
+ return this.fillForm(json, schema, false);
170
+ }
171
+ async fillForm(data, schema, dataIsTranscription = true) {
172
+ var _a, _b;
173
+ try {
174
+ // Handle complex schema format with fields array
175
+ if ((schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema)) {
176
+ const finalSchema = (schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema);
177
+ const systemPrompt = `You are an expert form-filling assistant. You will receive a voice transcription and form field definitions. Your task is to extract values from the spoken content.
178
+ Rules:
179
+ 1. Only extract values that can be confidently determined from the transcription
180
+ 2. Respect field types (string, number, datetime, boolean, select)
181
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
182
+ 4. For date fields, use the following format: DD/MM/YYY
183
+ 5. For boolean fields, interpret yes/no, true/false, positive/negative responses
184
+ 6. For select fields, use exact option values from the provided choices
185
+ 7. Only include fields where relevant information is found
186
+ 8. The current GMT datetime is ${new Date().toGMTString()}
187
+ 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
188
+
189
+ Respond with JSON in this exact format: {"fields": [{"name": "field_name", "value": "extracted_value"}]}`;
190
+ let userPrompt = `
191
+ Voice transcription: "${data}"
192
+
193
+ Form fields:
194
+ ${this.getOptimizeFieldsDescription(finalSchema)}
195
+
196
+ Please extract values from the Transcription for these fields.
197
+ `;
198
+ //TODO
199
+ // const userPrompt = (
200
+ // dataIsTranscription
201
+ // ?
202
+ // `Voice transcription: "${data}"`
203
+ // :
204
+ // `Json datas: "${JSON.stringify(data)}"`
205
+ // )+`
206
+ // Form fields:
207
+ // ${this.getOptimizeFieldsDescription(finalSchema)}
208
+ // Please extract values from the ` +
209
+ // (
210
+ // dataIsTranscription
211
+ // ?
212
+ // `Transcription`
213
+ // :
214
+ // `Json generated file`
215
+ // )+` for these fields.`;
216
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
217
+ method: 'POST',
218
+ headers: {
219
+ 'Content-Type': 'application/json',
220
+ 'Authorization': `Bearer ${this.apiKey}`,
221
+ },
222
+ body: JSON.stringify({
223
+ model: this.model,
224
+ messages: [
225
+ { role: 'system', content: systemPrompt },
226
+ { role: 'user', content: userPrompt }
227
+ ],
228
+ response_format: { type: 'json_object' },
229
+ temperature: 0.1,
230
+ }),
231
+ });
232
+ if (!response.ok) {
233
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
234
+ throw new Error(`LLM API failed: ${((_a = errorData.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
235
+ }
236
+ const result = await response.json();
237
+ return JSON.parse(result.choices[0].message.content);
238
+ }
239
+ // Handle simple schema format (backward compatibility)
240
+ const systemPrompt = `You are an expert form-filling assistant. You will receive a voice transcription and a JSON form schema. Your task is to intelligently fill the form fields based on the spoken content.
241
+ Rules:
242
+ 1. Only fill fields that can be confidently determined from the transcription
243
+ 2. Respect field types (string, number, date, boolean, select)
244
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
245
+ 4. For date fields, use the following format: DD/MM/YYY
246
+ 5. For boolean fields, interpret yes/no, true/false, positive/negative responses
247
+ 6. For select fields, match the closest option from the provided choices
248
+ 7. Leave fields empty if no relevant information is found
249
+ 8. Return the same schema structure with 'default' values filled
250
+ 9. The current GMT datetime is ${new Date().toGMTString()}
251
+ 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
252
+
253
+ Respond with JSON in this exact format: {"schema": {...}}`;
254
+ let userPrompt = `
255
+ Voice transcription: "${data}"
256
+
257
+ Form schema to fill:
258
+ ${JSON.stringify(schema, null, 2)}
259
+
260
+ Please fill the form fields based on the Transcription and return the schema with default values populated.
261
+ `;
262
+ // const userPrompt = (
263
+ // dataIsTranscription
264
+ // ?
265
+ // `Voice transcription: "${data}"`
266
+ // :
267
+ // `Json datas: "${JSON.stringify(data)}"`
268
+ // )+`
269
+ // Form schema to fill:
270
+ // ${JSON.stringify(schema, null, 2)}
271
+ // Please fill the form fields based on the ` +
272
+ // (
273
+ // dataIsTranscription
274
+ // ?
275
+ // `Transcription`
276
+ // :
277
+ // `Json generated file`
278
+ // )+` nd return the schema with default values populated.`;
279
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
280
+ method: 'POST',
281
+ headers: {
282
+ 'Content-Type': 'application/json',
283
+ 'Authorization': `Bearer ${this.apiKey}`,
284
+ },
285
+ body: JSON.stringify({
286
+ model: this.model,
287
+ messages: [
288
+ { role: 'system', content: systemPrompt },
289
+ { role: 'user', content: userPrompt }
290
+ ],
291
+ response_format: { type: 'json_object' },
292
+ temperature: 0.1, // Low temperature for consistency
293
+ }),
294
+ });
295
+ if (!response.ok) {
296
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
297
+ throw new Error(`LLM API failed: ${((_b = errorData.error) === null || _b === void 0 ? void 0 : _b.message) || response.statusText}`);
298
+ }
299
+ const result = await response.json();
300
+ const filledSchema = JSON.parse(result.choices[0].message.content);
301
+ // Validate that the response has the correct structure
302
+ if (!filledSchema.schema) {
303
+ throw new Error('Invalid response format from LLM service');
304
+ }
305
+ return filledSchema;
306
+ }
307
+ catch (error) {
308
+ throw new Error(`Form filling failed: ${error.message}`);
309
+ }
310
+ }
311
+ }
312
+ class LLMServiceFactory {
313
+ static create(config) {
314
+ var _a;
315
+ const provider = ((_a = config.llm) === null || _a === void 0 ? void 0 : _a.provider) || 'openai';
316
+ switch (provider) {
317
+ case 'openai':
318
+ return new OpenAILLMService(config.llm);
319
+ default:
320
+ throw new Error(`Unsupported LLM provider: ${provider}`);
321
+ }
322
+ }
323
+ }
324
+
325
+ class SchemaConverter {
326
+ /**
327
+ * Convert XML form definition to JSON schema
328
+ */
329
+ static convertXmlToJsonLegacy(xmlForm) {
330
+ try {
331
+ const parser = new DOMParser();
332
+ const xmlDoc = parser.parseFromString(xmlForm, 'text/xml');
333
+ if (xmlDoc.querySelector('parsererror')) {
334
+ throw new Error('Invalid XML format');
335
+ }
336
+ const schema = {
337
+ schema: {}
338
+ };
339
+ // Extract form title and description
340
+ const formElement = xmlDoc.querySelector('form');
341
+ if (formElement) {
342
+ schema.title = formElement.getAttribute('title') || undefined;
343
+ schema.description = formElement.getAttribute('description') || undefined;
344
+ }
345
+ // Process input fields
346
+ const inputs = xmlDoc.querySelectorAll('input, select, textarea');
347
+ inputs.forEach(input => {
348
+ const name = input.getAttribute('name');
349
+ if (!name)
350
+ return;
351
+ const field = {
352
+ type: this.mapXmlTypeToJsonTypeLegacy(input.tagName.toLowerCase(), input.getAttribute('type')),
353
+ title: input.getAttribute('title') || input.getAttribute('placeholder') || name,
354
+ description: input.getAttribute('description') || undefined,
355
+ required: input.hasAttribute('required'),
356
+ default: input.getAttribute('value') || undefined
357
+ };
358
+ // Handle select options
359
+ if (input.tagName.toLowerCase() === 'select') {
360
+ const options = Array.from(input.querySelectorAll('option')).map(option => option.textContent || option.getAttribute('value') || '');
361
+ if (options.length > 0) {
362
+ field.options = options;
363
+ }
364
+ }
365
+ // Handle validation attributes
366
+ const min = input.getAttribute('min');
367
+ const max = input.getAttribute('max');
368
+ const pattern = input.getAttribute('pattern');
369
+ if (min)
370
+ field.min = parseFloat(min);
371
+ if (max)
372
+ field.max = parseFloat(max);
373
+ if (pattern)
374
+ field.pattern = pattern;
375
+ schema.schema[name] = field;
376
+ });
377
+ return schema;
378
+ }
379
+ catch (error) {
380
+ throw new Error(`XML to JSON conversion failed: ${error.message}`);
381
+ }
382
+ }
383
+ /**
384
+ * Convert JSON schema to XML form definition
385
+ */
386
+ static convertJsonToXmlLegacy(jsonForm) {
387
+ try {
388
+ const doc = document.implementation.createDocument('', '', null);
389
+ const form = doc.createElement('form');
390
+ if (jsonForm.title) {
391
+ form.setAttribute('title', jsonForm.title);
392
+ }
393
+ if (jsonForm.description) {
394
+ form.setAttribute('description', jsonForm.description);
395
+ }
396
+ Object.entries(jsonForm.schema).forEach(([fieldName, field]) => {
397
+ var _a;
398
+ // Inject default value from root if present
399
+ const valueFromRoot = jsonForm[fieldName];
400
+ const fieldWithDefault = Object.assign(Object.assign({}, field), { default: (_a = field.default) !== null && _a !== void 0 ? _a : valueFromRoot // field.default has priority
401
+ });
402
+ const element = this.createXmlElementLegacy(doc, fieldName, fieldWithDefault);
403
+ if (element) {
404
+ form.appendChild(element);
405
+ }
406
+ });
407
+ doc.appendChild(form);
408
+ const serializer = new XMLSerializer();
409
+ return serializer.serializeToString(doc);
410
+ }
411
+ catch (error) {
412
+ throw new Error(`JSON to XML conversion failed: ${error.message}`);
413
+ }
414
+ }
415
+ static mapXmlTypeToJsonTypeLegacy(tagName, type) {
416
+ switch (tagName) {
417
+ case 'select':
418
+ return 'select';
419
+ case 'textarea':
420
+ return 'string';
421
+ case 'input':
422
+ switch (type) {
423
+ case 'number':
424
+ case 'range':
425
+ return 'number';
426
+ case 'date':
427
+ case 'datetime-local':
428
+ case 'time':
429
+ return 'date';
430
+ case 'checkbox':
431
+ return 'boolean';
432
+ default:
433
+ return 'string';
434
+ }
435
+ default:
436
+ return 'string';
437
+ }
438
+ }
439
+ static createXmlElementLegacy(doc, fieldName, field) {
440
+ let element;
441
+ switch (field.type) {
442
+ case 'select':
443
+ element = doc.createElement('select');
444
+ if (field.options) {
445
+ field.options.forEach(option => {
446
+ const optionElement = doc.createElement('option');
447
+ optionElement.setAttribute('value', option);
448
+ optionElement.textContent = option;
449
+ // Set selected if it matches the default value
450
+ if (field.default !== undefined && field.default === option) {
451
+ optionElement.setAttribute('selected', 'true');
452
+ }
453
+ element.appendChild(optionElement);
454
+ });
455
+ }
456
+ break;
457
+ case 'boolean':
458
+ element = doc.createElement('input');
459
+ element.setAttribute('type', 'checkbox');
460
+ if (field.default === true || field.default === 'true') {
461
+ element.setAttribute('checked', 'true');
462
+ }
463
+ break;
464
+ case 'number':
465
+ element = doc.createElement('input');
466
+ element.setAttribute('type', 'number');
467
+ if (field.min !== undefined)
468
+ element.setAttribute('min', field.min.toString());
469
+ if (field.max !== undefined)
470
+ element.setAttribute('max', field.max.toString());
471
+ if (field.default !== undefined)
472
+ element.setAttribute('value', field.default.toString());
473
+ break;
474
+ case 'date':
475
+ element = doc.createElement('input');
476
+ element.setAttribute('type', 'date');
477
+ if (field.default !== undefined)
478
+ element.setAttribute('value', field.default.toString());
479
+ break;
480
+ default: // string, text, etc.
481
+ element = doc.createElement('input');
482
+ element.setAttribute('type', 'text');
483
+ if (field.default !== undefined)
484
+ element.setAttribute('value', field.default.toString());
485
+ }
486
+ element.setAttribute('name', fieldName);
487
+ element.setAttribute('title', field.title);
488
+ if (field.description) {
489
+ element.setAttribute('description', field.description);
490
+ }
491
+ if (field.required) {
492
+ element.setAttribute('required', 'true');
493
+ }
494
+ if (field.pattern) {
495
+ element.setAttribute('pattern', field.pattern);
496
+ }
497
+ return element;
498
+ }
499
+ /**
500
+ * Convert new XML form definition to JSON schema
501
+ */
502
+ static async convertXmlToJson(xmlForm, classificationRootURL = "http://localhost", lang = 'en') {
503
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
504
+ try {
505
+ const parser = new DOMParser();
506
+ const xmlDoc = parser.parseFromString(xmlForm, 'text/xml');
507
+ if (xmlDoc.querySelector('parsererror')) {
508
+ throw new Error('Invalid XML format');
509
+ }
510
+ // Root Xforms element (optional)
511
+ const xforms = xmlDoc.querySelector('Xforms');
512
+ if (!xforms)
513
+ throw new Error('No Xforms root element found');
514
+ const fields = xforms.querySelectorAll('Fields');
515
+ if (fields.length === 0)
516
+ throw new Error('No Fields elements found');
517
+ const schema = {
518
+ fields: {}
519
+ };
520
+ for (const fieldEl of Array.from(fields)) {
521
+ // Read key info
522
+ const name = (_b = (_a = fieldEl.querySelector('SystemName')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim();
523
+ if (!name)
524
+ return;
525
+ const helpText = (_d = (_c = fieldEl.querySelector('HelpText')) === null || _c === void 0 ? void 0 : _c.textContent) === null || _d === void 0 ? void 0 : _d.trim();
526
+ const defaultValue = (_e = fieldEl.querySelector('DefaultValue')) === null || _e === void 0 ? void 0 : _e.textContent;
527
+ const enabledText = ((_g = (_f = fieldEl.querySelector('Enabled')) === null || _f === void 0 ? void 0 : _f.textContent) === null || _g === void 0 ? void 0 : _g.trim().toLowerCase()) || 'true';
528
+ const isEnabled = enabledText === 'true';
529
+ // keep as memory
530
+ const field_ID = (_h = fieldEl.querySelector('ID')) === null || _h === void 0 ? void 0 : _h.textContent;
531
+ const field_TFie_PK_ID = (_j = fieldEl.querySelector('TFie_PK_ID')) === null || _j === void 0 ? void 0 : _j.textContent;
532
+ const field_TVal_PK_ID = (_k = fieldEl.querySelector('TVal_PK_ID')) === null || _k === void 0 ? void 0 : _k.textContent;
533
+ const field_TFIn_PK_ID = (_l = fieldEl.querySelector('TFIn_PK_ID')) === null || _l === void 0 ? void 0 : _l.textContent;
534
+ const field_isForm = (_m = fieldEl.querySelector('isForm')) === null || _m === void 0 ? void 0 : _m.textContent;
535
+ const field_LabelText = (_o = fieldEl.querySelector('LabelText')) === null || _o === void 0 ? void 0 : _o.textContent;
536
+ const field_SystemName = (_p = fieldEl.querySelector('SystemName')) === null || _p === void 0 ? void 0 : _p.textContent;
537
+ const field_CssStyle = (_q = fieldEl.querySelector('CssStyle')) === null || _q === void 0 ? void 0 : _q.textContent;
538
+ const field_ControlType = ((_r = fieldEl.querySelector('ControlType')) === null || _r === void 0 ? void 0 : _r.textContent) || 'TextBox';
539
+ const field_ValidationRequired = (_s = fieldEl.querySelector('ValidationRequired')) === null || _s === void 0 ? void 0 : _s.textContent;
540
+ const field_ValidationMin = (_t = fieldEl.querySelector('ValidationMin')) === null || _t === void 0 ? void 0 : _t.textContent;
541
+ const field_ValidationMax = (_u = fieldEl.querySelector('ValidationMax')) === null || _u === void 0 ? void 0 : _u.textContent;
542
+ const field_Mask = (_v = fieldEl.querySelector('Mask')) === null || _v === void 0 ? void 0 : _v.textContent;
543
+ const field_TypeId = (_w = fieldEl.querySelector('TypeId')) === null || _w === void 0 ? void 0 : _w.textContent;
544
+ const field_Unit = (_x = fieldEl.querySelector('Unit')) === null || _x === void 0 ? void 0 : _x.textContent;
545
+ const field_TFie_Fullpath = (_y = fieldEl.querySelector('TFie_Fullpath')) === null || _y === void 0 ? void 0 : _y.textContent;
546
+ const field_TVal_FK_Parent_ID = (_z = fieldEl.querySelector('TVal_FK_Parent_ID')) === null || _z === void 0 ? void 0 : _z.textContent;
547
+ const field = {
548
+ type: this.mapControlTypeToJsonType(field_ControlType),
549
+ title: field_LabelText,
550
+ description: helpText,
551
+ required: (field_ValidationRequired === null || field_ValidationRequired === void 0 ? void 0 : field_ValidationRequired.toLowerCase()) === 'required',
552
+ default: defaultValue || undefined,
553
+ realType: field_ControlType,
554
+ Enabled: isEnabled,
555
+ ID: field_ID,
556
+ TFie_PK_ID: field_TFie_PK_ID,
557
+ TVal_PK_ID: field_TVal_PK_ID,
558
+ TFIn_PK_ID: field_TFIn_PK_ID,
559
+ isForm: field_isForm,
560
+ LabelText: field_LabelText,
561
+ SystemName: field_SystemName,
562
+ CssStyle: field_CssStyle,
563
+ ControlType: field_ControlType,
564
+ ValidationRequired: field_ValidationRequired,
565
+ ValidationMin: field_ValidationMin,
566
+ ValidationMax: field_ValidationMax,
567
+ Mask: field_Mask,
568
+ TypeId: field_TypeId,
569
+ Unit: field_Unit,
570
+ TFie_Fullpath: field_TFie_Fullpath,
571
+ TVal_FK_Parent_ID: field_TVal_FK_Parent_ID
572
+ };
573
+ // Handle select options
574
+ if (field.type.toLowerCase() === 'select') {
575
+ try {
576
+ const response = await fetch(`${classificationRootURL}/api/v1/classification/getList/${field.ControlType.toLowerCase() == 'termpicker' ? "position" : "thesaurus"}/?StartNodeID=${field_TypeId}&Language=${lang}`);
577
+ if (!response.ok)
578
+ throw new Error(`HTTP error! status: ${response.status}`);
579
+ const data = await response.json();
580
+ if (Array.isArray(data)) {
581
+ const options = data.map(item => ({
582
+ value: item.ID,
583
+ label: item.System_Name
584
+ }));
585
+ if (options.length > 0) {
586
+ field.pickerOptions = options;
587
+ // TODO on degage les quotes simples temporairement, a corriger
588
+ field.options = options.map(option => option.label.replace("'", ""));
589
+ }
590
+ }
591
+ else {
592
+ console.error("Unexpected API response format:", data);
593
+ }
594
+ }
595
+ catch (error) {
596
+ console.error("Error fetching classification data:", error);
597
+ }
598
+ }
599
+ if (field_ValidationMin)
600
+ field.min = parseFloat(field_ValidationMin);
601
+ if (field_ValidationMax)
602
+ field.max = parseFloat(field_ValidationMax);
603
+ if (field_Mask)
604
+ field.pattern = field_Mask;
605
+ // Optional: Add any other fields you want to track, e.g. min, max, pattern (not clearly in your XML)
606
+ // You could also add the raw ControlType for round-trip fidelity if you want
607
+ schema.fields[field_ID] = field;
608
+ }
609
+ return schema;
610
+ }
611
+ catch (error) {
612
+ throw new Error(`XML to JSON conversion failed: ${error.message}`);
613
+ }
614
+ }
615
+ /**
616
+ * Convert JSON schema back to your new XML form definition format
617
+ */
618
+ static convertJsonToXml(jsonForm) {
619
+ try {
620
+ const doc = document.implementation.createDocument('', '', null);
621
+ const xforms = doc.createElement('Xforms');
622
+ Object.entries((jsonForm.schema ? jsonForm.schema : jsonForm.fields)).forEach(([fieldID, field]) => {
623
+ const fieldEl = doc.createElement('Fields');
624
+ const fieldToUse = field;
625
+ // Set all subelements based on your XML structure
626
+ // ID, PK IDs etc are missing here, so omit or generate dummy
627
+ // <SystemName>
628
+ const systemNameEl = doc.createElement('SystemName');
629
+ systemNameEl.textContent = fieldToUse.SystemName;
630
+ fieldEl.appendChild(systemNameEl);
631
+ // <LabelText>
632
+ const labelEl = doc.createElement('LabelText');
633
+ labelEl.textContent = fieldToUse.title || fieldID;
634
+ fieldEl.appendChild(labelEl);
635
+ // <HelpText>
636
+ const helpEl = doc.createElement('HelpText');
637
+ helpEl.textContent = fieldToUse.description;
638
+ fieldEl.appendChild(helpEl);
639
+ // <ControlType>
640
+ const controlTypeEl = doc.createElement('ControlType');
641
+ controlTypeEl.textContent = fieldToUse.realType;
642
+ fieldEl.appendChild(controlTypeEl);
643
+ // <ValidationRequired>
644
+ const validationEl = doc.createElement('ValidationRequired');
645
+ validationEl.textContent = fieldToUse.required ? 'Required' : 'Not Required';
646
+ fieldEl.appendChild(validationEl);
647
+ // <DefaultValue>
648
+ const defaultEl = doc.createElement('DefaultValue');
649
+ const valueEl = doc.createElement('Value');
650
+ if (jsonForm[fieldID] !== undefined) {
651
+ defaultEl.textContent = jsonForm[fieldID].toString();
652
+ valueEl.textContent = jsonForm[fieldID].toString();
653
+ }
654
+ else {
655
+ defaultEl.textContent = fieldToUse.default || fieldToUse.value;
656
+ valueEl.textContent = fieldToUse.value || fieldToUse.default;
657
+ }
658
+ fieldEl.appendChild(defaultEl);
659
+ fieldEl.appendChild(valueEl);
660
+ // <Enabled>
661
+ const enabledEl = doc.createElement('Enabled');
662
+ enabledEl.textContent = fieldToUse.Enabled === false ? 'False' : 'True';
663
+ fieldEl.appendChild(enabledEl);
664
+ // Add <isForm> Field to distinguish Field or Header etc
665
+ const isFormEl = doc.createElement('isForm');
666
+ isFormEl.textContent = fieldToUse.isForm;
667
+ fieldEl.appendChild(isFormEl);
668
+ if (fieldToUse.type === 'header') {
669
+ const headerEl = doc.createElement('Header');
670
+ headerEl.textContent = fieldToUse.title;
671
+ fieldEl.appendChild(headerEl);
672
+ }
673
+ const idEl = doc.createElement('ID');
674
+ idEl.textContent = fieldToUse.ID;
675
+ fieldEl.appendChild(idEl);
676
+ const tfiePkIdEl = doc.createElement('TFie_PK_ID');
677
+ tfiePkIdEl.textContent = fieldToUse.TFie_PK_ID;
678
+ fieldEl.appendChild(tfiePkIdEl);
679
+ const tvalPkIdEl = doc.createElement('TVal_PK_ID');
680
+ tvalPkIdEl.textContent = fieldToUse.TVal_PK_ID;
681
+ fieldEl.appendChild(tvalPkIdEl);
682
+ const tfInPkIdEl = doc.createElement('TFIn_PK_ID');
683
+ tfInPkIdEl.textContent = fieldToUse.TFIn_PK_ID;
684
+ fieldEl.appendChild(tfInPkIdEl);
685
+ const cssStyleEl = doc.createElement('CssStyle');
686
+ cssStyleEl.textContent = fieldToUse.CssStyle;
687
+ fieldEl.appendChild(cssStyleEl);
688
+ const typeIdEl = doc.createElement('TypeId');
689
+ typeIdEl.textContent = fieldToUse.TypeId;
690
+ fieldEl.appendChild(typeIdEl);
691
+ const unitEl = doc.createElement('Unit');
692
+ unitEl.textContent = fieldToUse.Unit;
693
+ fieldEl.appendChild(unitEl);
694
+ const tfieFullPathEl = doc.createElement('TFie_Fullpath');
695
+ tfieFullPathEl.textContent = fieldToUse.TFie_Fullpath;
696
+ fieldEl.appendChild(tfieFullPathEl);
697
+ const tvalFkParentIdEl = doc.createElement('TVal_FK_Parent_ID');
698
+ tvalFkParentIdEl.textContent = fieldToUse.TVal_FK_Parent_ID;
699
+ fieldEl.appendChild(tvalFkParentIdEl);
700
+ const validationMinEl = doc.createElement('ValidationMin');
701
+ validationMinEl.textContent = fieldToUse.ValidationMin;
702
+ fieldEl.appendChild(validationMinEl);
703
+ const validationMaxEl = doc.createElement('ValidationMax');
704
+ validationMaxEl.textContent = fieldToUse.ValidationMax;
705
+ fieldEl.appendChild(validationMaxEl);
706
+ const maskEl = doc.createElement('Mask');
707
+ maskEl.textContent = fieldToUse.Mask;
708
+ fieldEl.appendChild(maskEl);
709
+ // Append to Xforms root
710
+ xforms.appendChild(fieldEl);
711
+ });
712
+ doc.appendChild(xforms);
713
+ const serializer = new XMLSerializer();
714
+ return serializer.serializeToString(doc);
715
+ }
716
+ catch (error) {
717
+ throw new Error(`JSON to XML conversion failed: ${error.message}`);
718
+ }
719
+ }
720
+ static mapControlTypeToJsonType(controlType) {
721
+ // Map your ControlType XML field to JSON field types
722
+ switch (controlType.toLowerCase()) {
723
+ case 'textbox':
724
+ case 'text':
725
+ return 'string';
726
+ case 'datepicker':
727
+ return 'date';
728
+ case 'thesauruspicker':
729
+ case 'thesauruspicker-ddl':
730
+ case 'termpicker':
731
+ return 'select';
732
+ case 'dbpicker':
733
+ return 'dbpicker'; // custom type if needed
734
+ case 'header':
735
+ return 'header'; // special type to handle header elements
736
+ case 'checkbox':
737
+ return 'boolean';
738
+ case 'realpicker':
739
+ return 'number';
740
+ default:
741
+ return 'string';
742
+ }
743
+ }
744
+ }
745
+
746
+ const voiceInputModuleCss = ":host{display:block;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif}.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;max-width:100px;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;max-width:800px;margin:0 auto}.record-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;border:none;border-radius:50%;background:#ee4444;color:white;cursor:pointer;transition:all 0.2s ease;position:relative;overflow:hidden}.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:flex;flex-direction:column;gap:1rem;margin-top:1rem}.form-group{display:flex;flex-direction:column;gap:0.25rem}.form-label{font-weight:500;color:#374151;font-size:0.875rem}.required{color:#ee4444;margin-left:0.125rem}.form-input{padding:0.5rem;border:1px solid #d1d5db;border-radius:0.375rem;font-size:0.875rem;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}.select-option{background:#e5e7eb;padding:0.25rem 0.5rem;border-radius:0.25rem;font-size:0.875rem;color:#374151}";
747
+
748
+ const VoiceFormRecorder = /*@__PURE__*/ proxyCustomElement(class VoiceFormRecorder extends HTMLElement {
749
+ constructor() {
750
+ super();
751
+ this.__registerHost();
752
+ this.__attachShadow();
753
+ this.formFilled = createEvent(this, "formFilled", 7);
754
+ this.recordingStateChanged = createEvent(this, "recordingStateChanged", 7);
755
+ this.formJson = '{}';
756
+ this.serviceConfig = '{}';
757
+ this.context = undefined;
758
+ this.classificationRootUrl = 'http://localhost';
759
+ this.language = 'en';
760
+ this.voiceOrOcr = undefined;
761
+ this.debug = false;
762
+ this.renderForm = false;
763
+ this.displayStatus = false;
764
+ this.isRecording = false;
765
+ this.isProcessing = false;
766
+ this.hasError = false;
767
+ this.transcription = '';
768
+ this.filledData = null;
769
+ this.debugInfo = {};
770
+ this.isReadonlyMode = true; // Start in readonly preview mode
771
+ this.audioRecorder = new AudioRecorderService();
772
+ }
773
+ componentWillLoad() {
774
+ this.initializeServices();
775
+ }
776
+ initializeServices() {
777
+ try {
778
+ if (!this.context) {
779
+ this.hasError = true;
780
+ const errorMessage = (this.language == 'en' ? `Initialization error: context is '${this.context}'` : `Erreur d'initialisation: le contexte est '${this.context}'`);
781
+ this.statusMessage = errorMessage;
782
+ this.updateDebugInfo(errorMessage, { error: errorMessage });
783
+ }
784
+ else {
785
+ // Parse form schema
786
+ this.parsedSchema = JSON.parse(this.formJson || '{}');
787
+ // Parse service configuration
788
+ this.parsedConfig = JSON.parse(this.serviceConfig || '{}');
789
+ // Add API key to config if provided via prop
790
+ if (this.apiKey) {
791
+ this.parsedConfig = Object.assign(Object.assign({}, this.parsedConfig), { speechToText: Object.assign(Object.assign({}, this.parsedConfig.speechToText), { apiKey: this.apiKey }), llm: Object.assign(Object.assign({}, this.parsedConfig.llm), { apiKey: this.apiKey }) });
792
+ }
793
+ // Initialize services
794
+ this.speechToTextService = SpeechToTextServiceFactory.create(this.parsedConfig);
795
+ this.llmService = LLMServiceFactory.create(this.parsedConfig);
796
+ this.updateDebugInfo('Initialized', {
797
+ schema: this.parsedSchema,
798
+ config: this.parsedConfig
799
+ });
800
+ this.hasError = false;
801
+ this.statusMessage = (this.language == 'en' ? 'Click to start recording' : 'Cliquer pour enregistrer');
802
+ }
803
+ }
804
+ catch (error) {
805
+ this.hasError = true;
806
+ this.statusMessage = (this.language == 'en' ? `Initialization error: ${error.message}` : `Erreur d'initialisation: ${error.message}`);
807
+ this.updateDebugInfo('Initialization Error', { error: error.message });
808
+ }
809
+ }
810
+ updateDebugInfo(action, data) {
811
+ if (this.debug) {
812
+ this.debugInfo = Object.assign(Object.assign({}, this.debugInfo), { [action]: {
813
+ timestamp: new Date().toISOString(),
814
+ data
815
+ } });
816
+ }
817
+ }
818
+ async handleRecordClick() {
819
+ if (this.isProcessing)
820
+ return;
821
+ if (this.isRecording) {
822
+ await this.stopRecordingAndProcess();
823
+ }
824
+ else {
825
+ await this.startRecording();
826
+ }
827
+ }
828
+ async startRecording() {
829
+ try {
830
+ this.hasError = false;
831
+ this.statusMessage = (this.language == 'en' ? 'Starting recording...' : `Enregistrement ...`);
832
+ this.updateDebugInfo('Start Recording Attempt', {});
833
+ await this.audioRecorder.startRecording();
834
+ this.isRecording = true;
835
+ this.statusMessage = (this.language == 'en' ? 'Recording... Click to stop' : 'Enregistrement ... Cliquer pour stopper');
836
+ this.updateDebugInfo('Recording Started', {});
837
+ this.recordingStateChanged.emit({
838
+ isRecording: true,
839
+ state: 'recording'
840
+ });
841
+ }
842
+ catch (error) {
843
+ this.hasError = true;
844
+ this.statusMessage = (this.language == 'en' ? `Recording failed: ${error.message}` : `Echec de l'enregistrement : ${error.message}`);
845
+ this.updateDebugInfo('Recording Error', { error: error.message });
846
+ this.formFilled.emit({
847
+ success: false,
848
+ error: error.message
849
+ });
850
+ }
851
+ }
852
+ async processJsonForm(jsonForm) {
853
+ // console.log("processJsonForm", jsonForm);
854
+ try {
855
+ this.isProcessing = true;
856
+ this.statusMessage = (this.language == 'en' ? 'Processing json...' : `Traitement du json ...`);
857
+ // Fill form using LLM
858
+ this.statusMessage = (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
859
+ const trimmedSchema = this.trimSchemaForAI(this.parsedSchema);
860
+ const filledSchema = await this.llmService.fillFormFromJson(jsonForm, trimmedSchema);
861
+ // Extract filled data
862
+ this.filledData = this.extractFilledData(filledSchema);
863
+ this.updateDebugInfo('Form Filled', {
864
+ filledSchema,
865
+ extractedData: this.filledData
866
+ });
867
+ this.parsedSchema = this.filledData;
868
+ this.statusMessage = (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
869
+ this.hasError = false;
870
+ // Emit success event
871
+ this.formFilled.emit({
872
+ success: true,
873
+ data: this.filledData,
874
+ jsonForm: jsonForm
875
+ });
876
+ }
877
+ catch (error) {
878
+ this.hasError = true;
879
+ this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
880
+ this.formFilled.emit({
881
+ success: false,
882
+ error: error.message,
883
+ jsonForm: jsonForm
884
+ });
885
+ }
886
+ finally {
887
+ this.isProcessing = false;
888
+ }
889
+ }
890
+ async stopRecordingAndProcess() {
891
+ try {
892
+ this.isRecording = false;
893
+ this.isProcessing = true;
894
+ this.statusMessage = (this.language == 'en' ? 'Processing audio...' : `Traitement de l'audio ...`);
895
+ this.updateDebugInfo('Stop Recording', {});
896
+ this.recordingStateChanged.emit({
897
+ isRecording: false,
898
+ state: 'processing'
899
+ });
900
+ // Stop recording and get audio blob
901
+ const audioBlob = await this.audioRecorder.stopRecording();
902
+ this.updateDebugInfo('Audio Captured', {
903
+ size: audioBlob.size,
904
+ type: audioBlob.type
905
+ });
906
+ // Transcribe audio
907
+ this.statusMessage = (this.language == 'en' ? 'Transcribing speech...' : 'Transcription du texte ...');
908
+ const transcription = await this.speechToTextService.transcribe(audioBlob, this.language);
909
+ this.transcription = transcription;
910
+ this.updateDebugInfo('Transcription Complete', { transcription });
911
+ if (!transcription.trim()) {
912
+ throw new Error('No speech detected in the recording');
913
+ }
914
+ // Fill form using LLM
915
+ this.statusMessage = (this.language == 'en' ? 'Filling form fields...' : 'Remplissage du formulaire ...');
916
+ const trimmedSchema = this.trimSchemaForAI(this.parsedSchema);
917
+ const filledSchema = await this.llmService.fillFormFromTranscription(transcription, trimmedSchema);
918
+ // Extract filled data
919
+ this.filledData = this.extractFilledData(filledSchema);
920
+ this.updateDebugInfo('Form Filled', {
921
+ filledSchema,
922
+ extractedData: this.filledData
923
+ });
924
+ this.parsedSchema = this.filledData;
925
+ this.statusMessage = (this.language == 'en' ? 'Form completed!' : 'Formulaire remplis !');
926
+ this.hasError = false;
927
+ // Emit success event
928
+ this.formFilled.emit({
929
+ success: true,
930
+ data: this.filledData,
931
+ transcription: transcription
932
+ });
933
+ }
934
+ catch (error) {
935
+ this.hasError = true;
936
+ this.statusMessage = (this.language == 'en' ? `Processing failed: ${error.message}` : `Erreur de traitement : ${error.message}`);
937
+ this.updateDebugInfo('Processing Error', { error: error.message });
938
+ this.formFilled.emit({
939
+ success: false,
940
+ error: error.message,
941
+ transcription: this.transcription
942
+ });
943
+ }
944
+ finally {
945
+ this.isProcessing = false;
946
+ this.recordingStateChanged.emit({
947
+ isRecording: false,
948
+ state: 'idle'
949
+ });
950
+ }
951
+ }
952
+ extractFilledData(filledData) {
953
+ // console.log("extractFilledData", filledData);
954
+ const updatedSchema = JSON.parse(JSON.stringify(this.parsedSchema));
955
+ switch (this.context) {
956
+ case "ng":
957
+ if (filledData === null || filledData === void 0 ? void 0 : filledData.fields) {
958
+ // Map AI response back to original schema structure
959
+ filledData.fields.forEach((field) => {
960
+ const originalField = updatedSchema.Children.find((child) => child.System_Name === field.name);
961
+ if (originalField && field.value !== undefined && field.value !== null && field.value !== '') {
962
+ if (!originalField.Settings)
963
+ originalField.Settings = {};
964
+ originalField.Settings.Default_Value = field.value;
965
+ }
966
+ });
967
+ }
968
+ break;
969
+ case "ecoll-veto":
970
+ // console.log("TODO extractFilledData", filledData, updatedSchema);
971
+ if (filledData === null || filledData === void 0 ? void 0 : filledData.fields) {
972
+ // Map AI response back to original schema structure
973
+ filledData.fields.forEach((field) => {
974
+ let originalField = updatedSchema[0].items.find((child) => child.label === field.name);
975
+ if (!originalField)
976
+ originalField = updatedSchema[1].items.find((child) => child.label === field.name);
977
+ if (originalField && field.value !== undefined && field.value !== null && field.value !== '') {
978
+ updatedSchema[2][originalField.name] = field.value;
979
+ }
980
+ });
981
+ }
982
+ break;
983
+ case "ecoteka":
984
+ // console.log("TODO extractFilledData", filledData);
985
+ break;
986
+ case "track":
987
+ default:
988
+ Object.entries(filledData.fields).forEach(([fieldID, field]) => {
989
+ if (field.default !== undefined && field.default !== null && field.default !== '') {
990
+ updatedSchema[fieldID] = field.default;
991
+ }
992
+ if (field.value !== undefined && field.value !== null && field.value !== '') {
993
+ for (const key in updatedSchema.fields) {
994
+ const schemaField = updatedSchema.fields[key];
995
+ if (schemaField.title === field.name) {
996
+ schemaField.value = field.value || field.default;
997
+ schemaField.default = field.value || field.default;
998
+ break; // stop after finding the first match
999
+ }
1000
+ }
1001
+ }
1002
+ });
1003
+ break;
1004
+ }
1005
+ // console.log("extractFilledData result", updatedSchema);
1006
+ return updatedSchema;
1007
+ }
1008
+ trimSchemaForAI(schema) {
1009
+ var _a, _b;
1010
+ // console.log("trimSchemaForAI", schema);
1011
+ switch (this.context) {
1012
+ case 'ng':
1013
+ const trimmed = { fields: [] };
1014
+ schema.Children.forEach((child) => {
1015
+ if (!child.System_Name || !child.Type)
1016
+ return;
1017
+ const fieldData = {
1018
+ name: child.System_Name,
1019
+ type: this.mapFieldType(child.Type)
1020
+ };
1021
+ // Add options for classification/select fields
1022
+ if (child.Type === 'InputClassification' && child.Children && child.Children.length > 0) {
1023
+ fieldData.options = child.Children.map((option) => option.System_Name || option.Label || option.toString());
1024
+ }
1025
+ trimmed.fields.push(fieldData);
1026
+ });
1027
+ // console.log("Schema apres transformation, contexte NG:", trimmed);
1028
+ return trimmed;
1029
+ case 'ecoll-veto':
1030
+ // console.log("TODO trimSchemaForAI", schema)
1031
+ const mergedItemsSchema = (this.parsedSchema[0].items).concat(this.parsedSchema[1].items);
1032
+ if (mergedItemsSchema) {
1033
+ const trimmedSchema = {
1034
+ title: 'Form Name',
1035
+ description: 'Form Description',
1036
+ schema: {}
1037
+ };
1038
+ Object.entries(mergedItemsSchema).forEach(([key, field]) => {
1039
+ const fieldName = field.name;
1040
+ const fieldType = this.mapFieldType(field.type);
1041
+ const fieldLabel = field.label || fieldName;
1042
+ trimmedSchema.schema[fieldName] = {
1043
+ type: fieldType,
1044
+ title: fieldLabel,
1045
+ options: field.options,
1046
+ readonly: field.readonly === true,
1047
+ default: '',
1048
+ };
1049
+ });
1050
+ // console.log("Schema apres transformation, contexte Track:", trimmedSchema);
1051
+ return trimmedSchema;
1052
+ }
1053
+ case "ecoteka":
1054
+ // console.log("TODO trimSchemaForAI", schema)
1055
+ break;
1056
+ case 'track':
1057
+ default:
1058
+ // Handle simple schema format (backward compatibility)
1059
+ if ((_a = schema === null || schema === void 0 ? void 0 : schema.schema) !== null && _a !== void 0 ? _a : schema === null || schema === void 0 ? void 0 : schema.fields) {
1060
+ const trimmedSchema = {
1061
+ title: schema.title,
1062
+ description: schema.description,
1063
+ schema: {}
1064
+ };
1065
+ const finalSchema = (_b = schema === null || schema === void 0 ? void 0 : schema.schema) !== null && _b !== void 0 ? _b : schema === null || schema === void 0 ? void 0 : schema.fields;
1066
+ Object.entries(finalSchema).forEach(([fieldName, field]) => {
1067
+ trimmedSchema.schema[fieldName] = {
1068
+ type: field.type,
1069
+ title: field.title,
1070
+ options: field.options,
1071
+ readonly: field.Enabled === false,
1072
+ default: field.DefaultValue,
1073
+ pattern: field.Mask,
1074
+ min: field.ValidationMin,
1075
+ max: field.ValidationMax
1076
+ };
1077
+ });
1078
+ // console.log("Schema apres transformation, contexte Track:", trimmedSchema);
1079
+ return trimmedSchema;
1080
+ }
1081
+ break;
1082
+ }
1083
+ return schema;
1084
+ }
1085
+ mapFieldType(type) {
1086
+ const typeMapping = {
1087
+ 'InputAutocomplete': 'string',
1088
+ 'InputInteger': 'number',
1089
+ 'InputTextArea': 'string',
1090
+ 'InputDateTimePicker': 'datetime',
1091
+ 'InputDecimal': 'number',
1092
+ 'InputClassification': 'select',
1093
+ 'InputCheckbox': 'boolean',
1094
+ 'InputTextTranslation': 'string',
1095
+ 'thesaurus': 'string',
1096
+ 'position': 'string',
1097
+ 'text': 'string',
1098
+ 'textarea': 'string',
1099
+ 'number': 'number',
1100
+ 'date': 'date',
1101
+ 'datetime': 'datetime'
1102
+ };
1103
+ return typeMapping[type] || 'string';
1104
+ }
1105
+ // Utility methods exposed as public API
1106
+ async convertXmlToJson(xmlForm) {
1107
+ return SchemaConverter.convertXmlToJson(xmlForm, this.classificationRootUrl, this.language);
1108
+ }
1109
+ async convertJsonToXml(jsonForm) {
1110
+ return SchemaConverter.convertJsonToXml(jsonForm);
1111
+ }
1112
+ // Utility methods exposed as public API
1113
+ async convertXmlToJsonLegacy(xmlForm) {
1114
+ return SchemaConverter.convertXmlToJsonLegacy(xmlForm);
1115
+ }
1116
+ async convertJsonToXmlLegacy(jsonForm) {
1117
+ return SchemaConverter.convertJsonToXmlLegacy(jsonForm);
1118
+ }
1119
+ renderUploadButton() {
1120
+ if (!['ocr', 'both'].includes(this.voiceOrOcr))
1121
+ return;
1122
+ return (h("file-uploader", { batch: false, callback: (data) => { this.processJsonForm(data); } }));
1123
+ }
1124
+ renderRecordButton() {
1125
+ if (!['voice', 'both', undefined].includes(this.voiceOrOcr))
1126
+ return;
1127
+ const buttonClass = [
1128
+ 'record-button',
1129
+ this.isRecording && 'recording',
1130
+ this.isProcessing && 'processing'
1131
+ ].filter(Boolean).join(' ');
1132
+ const isDisabled = this.isProcessing || this.hasError;
1133
+ return (h("button", { class: buttonClass, onClick: () => this.handleRecordClick(), disabled: isDisabled, "aria-label": this.isRecording ? 'Stop recording' : 'Start recording' }, this.isProcessing ? (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("circle", { cx: "12", cy: "12", r: "3" }, h("animate", { attributeName: "r", values: "3;6;3", dur: "1s", repeatCount: "indefinite" }), h("animate", { attributeName: "opacity", values: "1;0.3;1", dur: "1s", repeatCount: "indefinite" })))) : this.isRecording ? (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }))) : (h("svg", { class: "record-icon", viewBox: "0 0 24 24" }, h("circle", { cx: "12", cy: "12", r: "8" })))));
1134
+ }
1135
+ renderStatusMessage() {
1136
+ const statusClass = [
1137
+ 'status-text',
1138
+ this.hasError && 'error',
1139
+ this.filledData && !this.hasError && 'success'
1140
+ ].filter(Boolean).join(' ');
1141
+ return h("div", { class: statusClass }, this.statusMessage);
1142
+ }
1143
+ renderFormPreview() {
1144
+ if (!this.parsedSchema)
1145
+ return null;
1146
+ const isPreview = this.isReadonlyMode && !this.filledData;
1147
+ const title = isPreview ? 'Form Preview (Voice input to fill)' : 'Voice-Filled Form:';
1148
+ return (h("div", { class: "form-preview" }, h("div", { class: "form-preview-title" }, title), h("form", { class: "voice-filled-form" }, this.renderFormFields())));
1149
+ }
1150
+ renderFormFields() {
1151
+ // console.log("renderFormFields", this.parsedSchema);
1152
+ if (!this.parsedSchema)
1153
+ return null;
1154
+ switch (this.context) {
1155
+ case "ng":
1156
+ return this.parsedSchema.Children.map((child) => {
1157
+ var _a, _b, _c;
1158
+ if (!child.System_Name || !child.Type)
1159
+ return null;
1160
+ const fieldName = child.System_Name;
1161
+ const fieldType = this.mapFieldType(child.Type);
1162
+ const fieldLabel = ((_a = child.Settings) === null || _a === void 0 ? void 0 : _a.Label) || child.System_Name;
1163
+ const isRequired = child.Required || false;
1164
+ const fieldValue = (_b = child.Settings) === null || _b === void 0 ? void 0 : _b.Default_Value;
1165
+ return (h("div", { class: "form-group", key: fieldName }, h("label", { htmlFor: fieldName, class: "form-label" }, fieldLabel, isRequired && h("span", { class: "required" }, "*")), this.renderFormField(fieldName, {
1166
+ type: fieldType,
1167
+ title: fieldLabel,
1168
+ required: isRequired,
1169
+ options: (_c = child.Children) === null || _c === void 0 ? void 0 : _c.map((option) => option.System_Name || option.Label || option.toString())
1170
+ }, fieldValue)));
1171
+ }).filter(Boolean);
1172
+ case "ecoll-veto":
1173
+ // NOTE STEP 2
1174
+ // console.log("TODO renderFormFields", this.parsedSchema);
1175
+ const mergedItemsSchema = (this.parsedSchema[0].items).concat(this.parsedSchema[1].items);
1176
+ return Object.entries(mergedItemsSchema).map(([key, field]) => {
1177
+ var _a, _b;
1178
+ const fieldName = field.name;
1179
+ field.type = this.mapFieldType(field.type);
1180
+ const fieldLabel = field.label || fieldName;
1181
+ const isRequired = field.required || false;
1182
+ const fieldValue = this.parsedSchema[2][fieldName];
1183
+ return (h("div", { class: "form-group", key: fieldName }, h("label", { htmlFor: fieldName, class: "form-label" }, fieldLabel, isRequired && h("span", { class: "required" }, "*")), this.renderFormField(fieldName, field, ((_b = (_a = this.filledData) === null || _a === void 0 ? void 0 : _a[fieldName]) !== null && _b !== void 0 ? _b : fieldValue))));
1184
+ });
1185
+ case "ecoteka":
1186
+ // console.log("TODO renderFormFields", this.parsedSchema);
1187
+ break;
1188
+ case "track":
1189
+ default:
1190
+ return Object.entries(this.parsedSchema.fields).map(([fieldName, field]) => {
1191
+ var _a, _b;
1192
+ return (h("div", { class: "form-group", key: fieldName }, h("label", { htmlFor: fieldName, class: "form-label" }, field.title || fieldName, field.required && h("span", { class: "required" }, "*")), this.renderFormField(fieldName, field, ((_b = (_a = this.filledData) === null || _a === void 0 ? void 0 : _a[fieldName]) !== null && _b !== void 0 ? _b : field.value))));
1193
+ });
1194
+ }
1195
+ return null;
1196
+ }
1197
+ renderFormField(fieldName, field, value) {
1198
+ var _a, _b;
1199
+ const isReadonly = this.isReadonlyMode && !this.filledData;
1200
+ const commonProps = {
1201
+ id: fieldName,
1202
+ name: fieldName,
1203
+ class: 'form-input',
1204
+ required: field.required,
1205
+ disabled: isReadonly
1206
+ };
1207
+ switch (field.type) {
1208
+ case 'select':
1209
+ if (isReadonly) {
1210
+ // In readonly mode, show all options as a list instead of dropdown
1211
+ return (h("div", { class: "readonly-select" }, h("div", { class: "select-placeholder" }, "Available options:"), h("ul", { class: "select-options-list" }, (_a = field.options) === null || _a === void 0 ? void 0 : _a.map(option => (h("li", { class: "select-option" }, option))))));
1212
+ }
1213
+ return (h("select", { id: fieldName, name: fieldName, class: "form-input", required: field.required }, h("option", { value: "" }, "-- Select --"), (_b = field.options) === null || _b === void 0 ? void 0 :
1214
+ _b.map(option => (h("option", { value: option, selected: value === option }, option)))));
1215
+ case 'boolean':
1216
+ return (h("input", Object.assign({}, commonProps, { type: "checkbox", class: "form-checkbox", checked: value === true || value === 'true' })));
1217
+ case 'number':
1218
+ return (h("input", Object.assign({}, commonProps, { type: "number", min: field.min, max: field.max, step: "any", value: value || '' })));
1219
+ case 'date':
1220
+ return (h("input", Object.assign({}, commonProps, { type: 'date', value: value ? (([d, m, y]) => `${y}-${m}-${d}`)(value.split("/")) : '' })));
1221
+ case 'datetime':
1222
+ return (h("input", Object.assign({}, commonProps, { type: 'datetime-local', value: value || '' })));
1223
+ default: // string
1224
+ return (h("input", Object.assign({}, commonProps, { type: "text", pattern: field.pattern, placeholder: field.description, value: value || '' })));
1225
+ }
1226
+ }
1227
+ renderDebugPanel() {
1228
+ if (!this.debug)
1229
+ return null;
1230
+ return (h("div", { class: "debug-panel" }, h("div", { class: "debug-title" }, "Debug Information:"), h("div", { class: "debug-content" }, JSON.stringify(this.debugInfo, null, 2))));
1231
+ }
1232
+ render() {
1233
+ return (h("div", { key: '40d850c62945b71d22c1fcb6181c9e8aba0f2a05' }, h("div", { key: 'da8aa5b7f122471a8d4d49662d418bd925f1d84c', class: "voice-recorder-container" + (this.debug || this.renderForm ? "-debug" : "") }, this.renderRecordButton(), this.displayStatus ? this.renderStatusMessage() : "", this.renderUploadButton(), this.renderForm ? this.renderFormPreview() : "", this.debug ? this.renderDebugPanel() : "")));
1234
+ }
1235
+ static get watchers() { return {
1236
+ "formJson": ["initializeServices"],
1237
+ "serviceConfig": ["initializeServices"]
1238
+ }; }
1239
+ static get style() { return voiceInputModuleCss; }
1240
+ }, [257, "voice-input-module", {
1241
+ "formJson": [1, "form-json"],
1242
+ "serviceConfig": [1, "service-config"],
1243
+ "apiKey": [1, "api-key"],
1244
+ "context": [1],
1245
+ "classificationRootUrl": [1, "classification-root-url"],
1246
+ "language": [1],
1247
+ "voiceOrOcr": [1, "voice-or-ocr"],
1248
+ "debug": [4],
1249
+ "renderForm": [4, "render-form"],
1250
+ "displayStatus": [4, "display-status"],
1251
+ "isRecording": [32],
1252
+ "isProcessing": [32],
1253
+ "statusMessage": [32],
1254
+ "hasError": [32],
1255
+ "transcription": [32],
1256
+ "filledData": [32],
1257
+ "debugInfo": [32],
1258
+ "isReadonlyMode": [32],
1259
+ "convertXmlToJson": [64],
1260
+ "convertJsonToXml": [64],
1261
+ "convertXmlToJsonLegacy": [64],
1262
+ "convertJsonToXmlLegacy": [64]
1263
+ }, undefined, {
1264
+ "formJson": ["initializeServices"],
1265
+ "serviceConfig": ["initializeServices"]
1266
+ }]);
1267
+ function defineCustomElement$1() {
1268
+ if (typeof customElements === "undefined") {
1269
+ return;
1270
+ }
1271
+ const components = ["voice-input-module", "file-uploader"];
1272
+ components.forEach(tagName => { switch (tagName) {
1273
+ case "voice-input-module":
1274
+ if (!customElements.get(tagName)) {
1275
+ customElements.define(tagName, VoiceFormRecorder);
1276
+ }
1277
+ break;
1278
+ case "file-uploader":
1279
+ if (!customElements.get(tagName)) {
1280
+ defineCustomElement$2();
1281
+ }
1282
+ break;
1283
+ } });
1284
+ }
1285
+
1286
+ const VoiceInputModule = VoiceFormRecorder;
1287
+ const defineCustomElement = defineCustomElement$1;
1288
+
1289
+ export { VoiceInputModule, defineCustomElement };
1290
+ //# sourceMappingURL=voice-input-module.js.map
1291
+
1292
+ //# sourceMappingURL=voice-input-module.js.map