reneco-advanced-input-module 0.0.21 → 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 +274 -62
  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 +87 -4
  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 +137 -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 +281 -63
  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 +274 -62
  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-4e449895.entry.js +0 -3
  41. package/dist/voice-input-module/p-4e449895.entry.js.map +0 -1
  42. package/www/build/p-3a11e8d2.js +0 -2
  43. package/www/build/p-4e449895.entry.js +0 -3
  44. package/www/build/p-4e449895.entry.js.map +0 -1
@@ -3,64 +3,81 @@ export class AudioRecorderService {
3
3
  this.mediaRecorder = null;
4
4
  this.audioChunks = [];
5
5
  this.stream = null;
6
+ this.audioContext = null;
7
+ this.scriptProcessor = null;
8
+ this.pcmChunks = [];
9
+ this.sampleRate = 16000;
6
10
  }
7
11
  async startRecording() {
8
- try {
9
- // Check if the API exists before calling
10
- if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
11
- console.error('Failed to start recording:', 'Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');
12
- return; // Exit gracefully instead of throwing
13
- }
14
- this.stream = await navigator.mediaDevices.getUserMedia({
15
- audio: {
16
- echoCancellation: true,
17
- noiseSuppression: true,
18
- autoGainControl: true
19
- }
20
- });
21
- this.audioChunks = [];
22
- this.mediaRecorder = new MediaRecorder(this.stream, {
23
- mimeType: 'audio/webm;codecs=opus'
24
- });
25
- this.mediaRecorder.ondataavailable = (event) => {
26
- if (event.data && event.data.size > 0) {
27
- this.audioChunks.push(event.data);
28
- }
29
- };
30
- this.mediaRecorder.start(100); // Collect data every 100ms
31
- }
32
- catch (error) {
33
- console.error('Failed to start recording:', error);
12
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
13
+ throw new Error('Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');
34
14
  }
15
+ this.stream = await navigator.mediaDevices.getUserMedia({
16
+ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }
17
+ });
18
+ this.audioContext = new AudioContext({ sampleRate: this.sampleRate });
19
+ const source = this.audioContext.createMediaStreamSource(this.stream);
20
+ this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);
21
+ this.pcmChunks = [];
22
+ this.scriptProcessor.onaudioprocess = (e) => {
23
+ const input = e.inputBuffer.getChannelData(0);
24
+ this.pcmChunks.push(new Float32Array(input));
25
+ };
26
+ source.connect(this.scriptProcessor);
27
+ this.scriptProcessor.connect(this.audioContext.destination);
35
28
  }
36
29
  async stopRecording() {
37
- return new Promise((resolve, reject) => {
38
- if (!this.mediaRecorder) {
39
- reject(new Error('No active recording found'));
40
- return;
30
+ if (!this.audioContext || !this.scriptProcessor) {
31
+ throw new Error('No active recording found');
32
+ }
33
+ this.scriptProcessor.disconnect();
34
+ await this.audioContext.close();
35
+ const wavBlob = this.encodeWav(this.pcmChunks, this.sampleRate);
36
+ this.cleanup();
37
+ return wavBlob;
38
+ }
39
+ encodeWav(chunks, sampleRate) {
40
+ const totalSamples = chunks.reduce((acc, c) => acc + c.length, 0);
41
+ const buffer = new ArrayBuffer(44 + totalSamples * 2);
42
+ const view = new DataView(buffer);
43
+ const writeStr = (offset, str) => {
44
+ for (let i = 0; i < str.length; i++)
45
+ view.setUint8(offset + i, str.charCodeAt(i));
46
+ };
47
+ writeStr(0, 'RIFF');
48
+ view.setUint32(4, 36 + totalSamples * 2, true);
49
+ writeStr(8, 'WAVE');
50
+ writeStr(12, 'fmt ');
51
+ view.setUint32(16, 16, true); // PCM chunk size
52
+ view.setUint16(20, 1, true); // PCM format
53
+ view.setUint16(22, 1, true); // mono
54
+ view.setUint32(24, sampleRate, true);
55
+ view.setUint32(28, sampleRate * 2, true); // byte rate
56
+ view.setUint16(32, 2, true); // block align
57
+ view.setUint16(34, 16, true); // bits per sample
58
+ writeStr(36, 'data');
59
+ view.setUint32(40, totalSamples * 2, true);
60
+ let offset = 44;
61
+ for (const chunk of chunks) {
62
+ for (let i = 0; i < chunk.length; i++) {
63
+ const s = Math.max(-1, Math.min(1, chunk[i]));
64
+ view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
65
+ offset += 2;
41
66
  }
42
- this.mediaRecorder.onstop = () => {
43
- const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
44
- this.cleanup();
45
- resolve(audioBlob);
46
- };
47
- this.mediaRecorder.onerror = (event) => {
48
- reject(new Error(`Recording error: ${event}`));
49
- };
50
- this.mediaRecorder.stop();
51
- });
67
+ }
68
+ return new Blob([buffer], { type: 'audio/wav' });
52
69
  }
53
70
  isRecording() {
54
- var _a;
55
- return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
71
+ return this.scriptProcessor !== null;
56
72
  }
57
73
  cleanup() {
58
74
  if (this.stream) {
59
75
  this.stream.getTracks().forEach(track => track.stop());
60
76
  this.stream = null;
61
77
  }
62
- this.mediaRecorder = null;
63
- this.audioChunks = [];
78
+ this.audioContext = null;
79
+ this.scriptProcessor = null;
80
+ this.pcmChunks = [];
64
81
  }
65
82
  }
66
83
  //# sourceMappingURL=audio-recorder.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio-recorder.service.js","sourceRoot":"","sources":["../../src/services/audio-recorder.service.ts"],"names":[],"mappings":"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","sourcesContent":["export class AudioRecorderService {\r\n private mediaRecorder: MediaRecorder | null = null;\r\n private audioChunks: Blob[] = [];\r\n private stream: MediaStream | null = null;\r\n\r\n async startRecording(): Promise<void> {\r\n try {\r\n // Check if the API exists before calling\r\n if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\r\n console.error(\r\n 'Failed to start recording:', 'Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.'\r\n );\r\n return; // Exit gracefully instead of throwing\r\n }\r\n \r\n this.stream = await navigator.mediaDevices.getUserMedia({\r\n audio: {\r\n echoCancellation: true,\r\n noiseSuppression: true,\r\n autoGainControl: true\r\n }\r\n });\r\n\r\n this.audioChunks = [];\r\n this.mediaRecorder = new MediaRecorder(this.stream, {\r\n mimeType: 'audio/webm;codecs=opus'\r\n });\r\n\r\n this.mediaRecorder.ondataavailable = (event) => {\r\n if (event.data && event.data.size > 0) {\r\n this.audioChunks.push(event.data);\r\n }\r\n };\r\n\r\n this.mediaRecorder.start(100); // Collect data every 100ms\r\n } catch (error: any) {\r\n console.error('Failed to start recording:', error);\r\n }\r\n }\r\n\r\n async stopRecording(): Promise<Blob> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.mediaRecorder) {\r\n reject(new Error('No active recording found'));\r\n return;\r\n }\r\n\r\n this.mediaRecorder.onstop = () => {\r\n const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });\r\n this.cleanup();\r\n resolve(audioBlob);\r\n };\r\n\r\n this.mediaRecorder.onerror = (event) => {\r\n reject(new Error(`Recording error: ${event}`));\r\n };\r\n\r\n this.mediaRecorder.stop();\r\n });\r\n }\r\n\r\n isRecording(): boolean {\r\n return this.mediaRecorder?.state === 'recording';\r\n }\r\n\r\n private cleanup(): void {\r\n if (this.stream) {\r\n this.stream.getTracks().forEach(track => track.stop());\r\n this.stream = null;\r\n }\r\n this.mediaRecorder = null;\r\n this.audioChunks = [];\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"audio-recorder.service.js","sourceRoot":"","sources":["../../src/services/audio-recorder.service.ts"],"names":[],"mappings":"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","sourcesContent":["export class AudioRecorderService {\r\n private mediaRecorder: MediaRecorder | null = null;\r\n private audioChunks: Blob[] = [];\r\n private stream: MediaStream | null = null;\r\n private audioContext: AudioContext | null = null;\r\n private scriptProcessor: ScriptProcessorNode | null = null;\r\n private pcmChunks: Float32Array[] = [];\r\n private sampleRate: number = 16000;\r\n\r\n async startRecording(): Promise<void> {\r\n if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\r\n throw new Error('Microphone access is not supported in this browser or the page is not served over HTTPS/localhost.');\r\n }\r\n\r\n this.stream = await navigator.mediaDevices.getUserMedia({\r\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }\r\n });\r\n\r\n this.audioContext = new AudioContext({ sampleRate: this.sampleRate });\r\n const source = this.audioContext.createMediaStreamSource(this.stream);\r\n this.scriptProcessor = this.audioContext.createScriptProcessor(4096, 1, 1);\r\n this.pcmChunks = [];\r\n\r\n this.scriptProcessor.onaudioprocess = (e) => {\r\n const input = e.inputBuffer.getChannelData(0);\r\n this.pcmChunks.push(new Float32Array(input));\r\n };\r\n\r\n source.connect(this.scriptProcessor);\r\n this.scriptProcessor.connect(this.audioContext.destination);\r\n }\r\n\r\n async stopRecording(): Promise<Blob> {\r\n if (!this.audioContext || !this.scriptProcessor) {\r\n throw new Error('No active recording found');\r\n }\r\n\r\n this.scriptProcessor.disconnect();\r\n await this.audioContext.close();\r\n\r\n const wavBlob = this.encodeWav(this.pcmChunks, this.sampleRate);\r\n this.cleanup();\r\n return wavBlob;\r\n }\r\n\r\n private encodeWav(chunks: Float32Array[], sampleRate: number): Blob {\r\n const totalSamples = chunks.reduce((acc, c) => acc + c.length, 0);\r\n const buffer = new ArrayBuffer(44 + totalSamples * 2);\r\n const view = new DataView(buffer);\r\n\r\n const writeStr = (offset: number, str: string) => {\r\n for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));\r\n };\r\n\r\n writeStr(0, 'RIFF');\r\n view.setUint32(4, 36 + totalSamples * 2, true);\r\n writeStr(8, 'WAVE');\r\n writeStr(12, 'fmt ');\r\n view.setUint32(16, 16, true); // PCM chunk size\r\n view.setUint16(20, 1, true); // PCM format\r\n view.setUint16(22, 1, true); // mono\r\n view.setUint32(24, sampleRate, true);\r\n view.setUint32(28, sampleRate * 2, true); // byte rate\r\n view.setUint16(32, 2, true); // block align\r\n view.setUint16(34, 16, true); // bits per sample\r\n writeStr(36, 'data');\r\n view.setUint32(40, totalSamples * 2, true);\r\n\r\n let offset = 44;\r\n for (const chunk of chunks) {\r\n for (let i = 0; i < chunk.length; i++) {\r\n const s = Math.max(-1, Math.min(1, chunk[i]));\r\n view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);\r\n offset += 2;\r\n }\r\n }\r\n\r\n return new Blob([buffer], { type: 'audio/wav' });\r\n }\r\n\r\n isRecording(): boolean {\r\n return this.scriptProcessor !== null;\r\n }\r\n\r\n private cleanup(): void {\r\n if (this.stream) {\r\n this.stream.getTracks().forEach(track => track.stop());\r\n this.stream = null;\r\n }\r\n this.audioContext = null;\r\n this.scriptProcessor = null;\r\n this.pcmChunks = [];\r\n }\r\n}\r\n"]}
@@ -35,7 +35,8 @@ export class OpenAILLMService {
35
35
  `${field.readonly ? ', readonly' : ''}` +
36
36
  `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default=' + field.default : ''}` +
37
37
  `${field.min && field.min !== "" ? ', min=' + field.min : ''}` +
38
- `${field.max && field.max !== "" ? ', max=' + field.max : ''}`;
38
+ `${field.max && field.max !== "" ? ', max=' + field.max : ''}` +
39
+ `${field.pattern && field.pattern !== "" ? ', pattern=' + field.pattern : ''}`;
39
40
  if (field.options && field.options.length > 0) {
40
41
  if (field.options.length < 50) {
41
42
  description += `) - options: ${field.options.join(', ')}`;
@@ -57,7 +58,8 @@ export class OpenAILLMService {
57
58
  `${field.readonly ? ', readonly' : ''}` +
58
59
  `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default=' + field.default : ''}` +
59
60
  `${field.min && field.min !== "" ? ', min=' + field.min : ''}` +
60
- `${field.max && field.max !== "" ? ', max=' + field.max : ''}`;
61
+ `${field.max && field.max !== "" ? ', max=' + field.max : ''}` +
62
+ `${field.pattern && field.pattern !== "" ? ', pattern=' + field.pattern : ''}`;
61
63
  if (field.options && field.options.length > 0) {
62
64
  if (field.options.length < 50) {
63
65
  description += `) - options: ${field.options.join(', ')}`;
@@ -201,12 +203,14 @@ export class OpenAILLMService {
201
203
  7. Only include fields where relevant information is found
202
204
  8. The current GMT datetime is ${new Date().toGMTString()}
203
205
  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
206
+ 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
204
207
  10. IMPORTANT: Fields marked as "readonly" are presentation elements (headers, labels) that provide context but MUST NOT receive values
205
208
  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
206
209
  12. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them
207
- 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"
210
+ 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
208
211
  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
209
212
  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
213
+ 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
210
214
 
211
215
  Respond with JSON in this exact format: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
212
216
  let userPrompt = `
@@ -275,10 +279,12 @@ export class OpenAILLMService {
275
279
  8. Return the same schema structure with 'default' values filled
276
280
  9. The current GMT datetime is ${new Date().toGMTString()}
277
281
  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
282
+ 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
278
283
  11. IMPORTANT: Fields marked as "readonly" are presentation elements (headers, labels) that provide context but MUST NOT receive values
279
284
  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
280
285
  13. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them
281
- 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"
286
+ 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
287
+ 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
282
288
 
283
289
  Respond with JSON in this exact format: {"schema": {...}}`;
284
290
  let userPrompt = `
@@ -342,15 +348,138 @@ export class OpenAILLMService {
342
348
  }
343
349
  }
344
350
  }
351
+ export class AnthropicLLMService {
352
+ constructor(config) {
353
+ this.useProxy = (config === null || config === void 0 ? void 0 : config.useProxy) || false;
354
+ this.proxyUrl = (config === null || config === void 0 ? void 0 : config.proxyUrl) || 'http://localhost:8492';
355
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'claude-sonnet-4.5';
356
+ this.apiKey = this.useProxy ? '' : ((config === null || config === void 0 ? void 0 : config.apiKey) || '');
357
+ if (!this.useProxy && !this.apiKey) {
358
+ throw new Error('Anthropic API key is required');
359
+ }
360
+ }
361
+ async fillFormFromTranscription(transcription, schema) {
362
+ return this.fillForm(transcription, schema);
363
+ }
364
+ async fillFormFromJson(json, schema) {
365
+ return this.fillForm(json, schema);
366
+ }
367
+ async fillForm(data, schema) {
368
+ var _a;
369
+ const endpoint = this.useProxy ? `${this.proxyUrl}/complete-anthropic` : 'https://api.anthropic.com/v1/messages';
370
+ const headers = { 'Content-Type': 'application/json' };
371
+ if (!this.useProxy) {
372
+ headers['x-api-key'] = this.apiKey;
373
+ headers['anthropic-version'] = '2023-06-01';
374
+ }
375
+ const finalSchema = (schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema) || schema;
376
+ const systemPrompt = this.buildSystemPrompt();
377
+ const userPrompt = `Data: "${data}"
378
+
379
+ Form fields:
380
+ ${JSON.stringify(finalSchema, null, 2)}
381
+
382
+ Respond with JSON: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
383
+ const body = this.useProxy
384
+ ? { model: this.model, messages: [{ role: 'user', content: userPrompt }], system: systemPrompt }
385
+ : { model: this.model, max_tokens: 4096, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] };
386
+ const response = await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });
387
+ if (!response.ok) {
388
+ const err = await response.json().catch(() => ({ error: 'Unknown error' }));
389
+ throw new Error(`Anthropic API failed: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
390
+ }
391
+ const result = await response.json();
392
+ const content = this.useProxy ? result.choices[0].message.content : result.content[0].text;
393
+ return JSON.parse(content);
394
+ }
395
+ buildSystemPrompt() {
396
+ return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.
397
+ Rules:
398
+ 1. Only extract values that can be confidently determined
399
+ 2. Respect field types (string, number, datetime, boolean, select)
400
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
401
+ 4. For date fields, use DD/MM/YYYY format
402
+ 5. For select fields, use exact option values from the provided choices
403
+ 6. Leave fields empty if no relevant information is found
404
+ 7. Fields marked readonly MUST NOT receive values
405
+ 8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY
406
+ 9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty
407
+ 10. UNKNOWN VALUES: if the user says "Unknown" and it exists as an option, select it; otherwise leave empty
408
+ Respond ONLY with valid JSON.`;
409
+ }
410
+ }
411
+ export class MistralLLMService {
412
+ constructor(config) {
413
+ this.useProxy = (config === null || config === void 0 ? void 0 : config.useProxy) || false;
414
+ this.proxyUrl = (config === null || config === void 0 ? void 0 : config.proxyUrl) || 'http://localhost:8492';
415
+ this.model = (config === null || config === void 0 ? void 0 : config.model) || 'mistral-medium-latest';
416
+ this.apiKey = this.useProxy ? '' : ((config === null || config === void 0 ? void 0 : config.apiKey) || '');
417
+ if (!this.useProxy && !this.apiKey) {
418
+ throw new Error('Mistral API key is required');
419
+ }
420
+ }
421
+ async fillFormFromTranscription(transcription, schema) {
422
+ return this.fillForm(transcription, schema);
423
+ }
424
+ async fillFormFromJson(json, schema) {
425
+ return this.fillForm(json, schema);
426
+ }
427
+ async fillForm(data, schema) {
428
+ var _a;
429
+ const endpoint = this.useProxy ? `${this.proxyUrl}/complete-mistral` : 'https://api.mistral.ai/v1/chat/completions';
430
+ const headers = { 'Content-Type': 'application/json' };
431
+ if (!this.useProxy) {
432
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
433
+ }
434
+ const finalSchema = (schema === null || schema === void 0 ? void 0 : schema.fields) || (schema === null || schema === void 0 ? void 0 : schema.schema) || schema;
435
+ const systemPrompt = this.buildSystemPrompt();
436
+ const userPrompt = `Data: "${data}"
437
+
438
+ Form fields:
439
+ ${JSON.stringify(finalSchema, null, 2)}
440
+
441
+ Respond with JSON: {"fields": [{"id": "field_id", "name": "field_name", "value": "extracted_value"}]}`;
442
+ const response = await fetch(endpoint, {
443
+ method: 'POST',
444
+ headers,
445
+ body: JSON.stringify({
446
+ model: this.model,
447
+ messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }],
448
+ response_format: { type: 'json_object' },
449
+ }),
450
+ });
451
+ if (!response.ok) {
452
+ const err = await response.json().catch(() => ({ error: 'Unknown error' }));
453
+ throw new Error(`Mistral API failed: ${((_a = err.error) === null || _a === void 0 ? void 0 : _a.message) || response.statusText}`);
454
+ }
455
+ const result = await response.json();
456
+ return JSON.parse(result.choices[0].message.content);
457
+ }
458
+ buildSystemPrompt() {
459
+ return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.
460
+ Rules:
461
+ 1. Only extract values that can be confidently determined
462
+ 2. Respect field types (string, number, datetime, boolean, select)
463
+ 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)
464
+ 4. For date fields, use DD/MM/YYYY format
465
+ 5. For select fields, use exact option values from the provided choices
466
+ 6. Leave fields empty if no relevant information is found
467
+ 7. Fields marked readonly MUST NOT receive values
468
+ 8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY
469
+ 9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty
470
+ 10. UNKNOWN VALUES: if the user says "Unknown" and it exists as an option, select it; otherwise leave empty
471
+ Respond ONLY with valid JSON.`;
472
+ }
473
+ }
345
474
  export class LLMServiceFactory {
346
475
  static create(config) {
347
476
  var _a;
348
477
  const provider = ((_a = config.llm) === null || _a === void 0 ? void 0 : _a.provider) || 'openai';
349
478
  switch (provider) {
350
- case 'openai':
351
- return new OpenAILLMService(config.llm);
352
- default:
353
- throw new Error(`Unsupported LLM provider: ${provider}`);
479
+ case 'openai': return new OpenAILLMService(config.llm);
480
+ case 'anthropic': return new AnthropicLLMService(config.llm);
481
+ case 'mistral': return new MistralLLMService(config.llm);
482
+ default: throw new Error(`Unsupported LLM provider: ${provider}`);
354
483
  }
355
484
  }
356
485
  }
@@ -1 +1 @@
1
- {"version":3,"file":"llm.service.js","sourceRoot":"","sources":["../../src/services/llm.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { LLMProvider, ServiceProviderConfig } from '../types/service-providers.types';\r\nimport { FormSchema } from '../types/form-schema.types';\r\nimport { fuzzyMatchOption } from '../utils/fuzzy-match';\r\n\r\nexport class OpenAILLMService implements LLMProvider {\r\n private apiKey: string;\r\n private model: string;\r\n private baseUrl: string;\r\n private useProxy: boolean;\r\n private proxyUrl: string;\r\n\r\n constructor(config: ServiceProviderConfig['llm']) {\r\n this.useProxy = config?.useProxy || false;\r\n this.proxyUrl = config?.proxyUrl || 'http://localhost:8492';\r\n \r\n if (this.useProxy) {\r\n // Mode proxy: pas besoin d'API key\r\n this.apiKey = '';\r\n this.baseUrl = this.proxyUrl;\r\n } else {\r\n // Mode direct: API key requise\r\n this.apiKey = config?.apiKey || this.getEnvironmentVariable('OPENAI_API_KEY') || '';\r\n this.baseUrl = config?.baseUrl || 'https://api.openai.com/v1';\r\n \r\n if (!this.apiKey) {\r\n throw new Error('OpenAI API key is required for LLM service');\r\n }\r\n }\r\n \r\n this.model = config?.model || 'gpt-5-mini';\r\n }\r\n\r\n private getEnvironmentVariable(name: string): string | undefined {\r\n // In browser environment, we might get env vars through other means\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[name];\r\n }\r\n // Check if it's available as a global variable or through other means\r\n return (window as any)[name] || undefined;\r\n }\r\n\r\n private getOptimizeFieldsDescription(schema: any){\r\n if (Array.isArray(schema)) {\r\n return schema.map((field: any) => {\r\n let description = `- ID:${field.id || field.name} | ${field.name || field.title} ` +\r\n `(${field.type}` +\r\n `${field.required ? ', required' : ''}` +\r\n `${field.readonly ? ', readonly' : ''}` +\r\n `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default='+field.default : ''}` +\r\n `${field.min && field.min !== \"\" ? ', min='+field.min : ''}` +\r\n `${field.max && field.max !== \"\" ? ', max='+field.max : ''}`;\r\n \r\n if (field.options && field.options.length > 0) {\r\n if (field.options.length < 50) {\r\n description += `) - options: ${field.options.join(', ')}`;\r\n } else {\r\n description += `, many options: ${field.options.length} total) - extract raw value, will be matched locally`;\r\n }\r\n } else {\r\n description += ')';\r\n }\r\n \r\n return description;\r\n }).join('\\n');\r\n }\r\n \r\n return Object.entries(schema).map(([fieldName, field]: [string, any]) => {\r\n let description = `- ID:${field.id || fieldName} | ${field.title || fieldName} ` +\r\n `(${field.type}` +\r\n `${field.required ? ', required' : ''}` +\r\n `${field.readonly ? ', readonly' : ''}` +\r\n `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default='+field.default : ''}` +\r\n `${field.min && field.min !== \"\" ? ', min='+field.min : ''}` +\r\n `${field.max && field.max !== \"\" ? ', max='+field.max : ''}`;\r\n \r\n if (field.options && field.options.length > 0) {\r\n if (field.options.length < 50) {\r\n description += `) - options: ${field.options.join(', ')}`;\r\n } else {\r\n description += `, many options: ${field.options.length} total) - extract raw value, will be matched locally`;\r\n }\r\n } else {\r\n description += ')';\r\n }\r\n \r\n return description;\r\n }).join('\\n');\r\n }\r\n\r\n async fillFormFromTranscription(transcription: string, schema: any): Promise<any> {\r\n return this.fillForm(transcription, schema, true);\r\n }\r\n\r\n private applyFuzzyMatching(llmResponse: any, schema: any): any {\r\n // Handle complex schema format with fields array\r\n if (schema?.fields || schema?.schema) {\r\n const finalSchema = schema?.fields || schema?.schema;\r\n const fieldsWithErrors: string[] = [];\r\n \r\n if (llmResponse.fields && Array.isArray(llmResponse.fields)) {\r\n llmResponse.fields = llmResponse.fields.map((field: any) => {\r\n // Find corresponding schema field\r\n const schemaField = Object.values(finalSchema).find((sf: any) => \r\n (sf.name === field.name || sf.title === field.name)\r\n ) as any;\r\n \r\n if (schemaField && schemaField.options && schemaField.options.length >= 50 && field.value) {\r\n const isMultiselect = schemaField.type === 'multiselect';\r\n \r\n // Handle multiselect (array of values)\r\n if (isMultiselect && Array.isArray(field.value)) {\r\n const matchedValues: string[] = [];\r\n const failedValues: string[] = [];\r\n \r\n field.value.forEach((val: string) => {\r\n const matchResult = fuzzyMatchOption(val, schemaField.options);\r\n if (matchResult) {\r\n matchedValues.push(matchResult.match);\r\n } else {\r\n failedValues.push(val);\r\n }\r\n });\r\n \r\n if (failedValues.length > 0) {\r\n fieldsWithErrors.push(field.name);\r\n field.error = `Could not match values: ${failedValues.join(', ')}`;\r\n }\r\n \r\n field.value = matchedValues.length > 0 ? matchedValues : null;\r\n field.fuzzyMatchConfidence = matchedValues.length / (matchedValues.length + failedValues.length);\r\n } else if (!isMultiselect) {\r\n // Handle single value (select, classification, thesaurus, position)\r\n const singleValue = Array.isArray(field.value) ? field.value[0] : field.value;\r\n const matchResult = fuzzyMatchOption(singleValue, schemaField.options);\r\n \r\n if (matchResult) {\r\n field.value = matchResult.match;\r\n field.fuzzyMatchConfidence = matchResult.confidence;\r\n } else {\r\n fieldsWithErrors.push(field.name);\r\n field.value = null;\r\n field.error = `Could not match \"${singleValue}\" to available options`;\r\n }\r\n }\r\n }\r\n \r\n return field;\r\n });\r\n }\r\n \r\n if (fieldsWithErrors.length > 0) {\r\n llmResponse.matchingErrors = fieldsWithErrors;\r\n }\r\n \r\n return llmResponse;\r\n }\r\n \r\n // Handle simple schema format\r\n if (schema?.schema) {\r\n const fieldsWithErrors: string[] = [];\r\n \r\n Object.entries(schema.schema).forEach(([fieldName, fieldDef]: [string, any]) => {\r\n if (fieldDef.options && fieldDef.options.length >= 50) {\r\n const fieldValue = llmResponse.schema?.[fieldName]?.default;\r\n \r\n if (fieldValue) {\r\n const isMultiselect = fieldDef.type === 'multiselect';\r\n \r\n // Handle multiselect (array of values)\r\n if (isMultiselect && Array.isArray(fieldValue)) {\r\n const matchedValues: string[] = [];\r\n const failedValues: string[] = [];\r\n \r\n fieldValue.forEach((val: string) => {\r\n const matchResult = fuzzyMatchOption(val, fieldDef.options);\r\n if (matchResult) {\r\n matchedValues.push(matchResult.match);\r\n } else {\r\n failedValues.push(val);\r\n }\r\n });\r\n \r\n if (failedValues.length > 0) {\r\n fieldsWithErrors.push(fieldName);\r\n llmResponse.schema[fieldName].error = `Could not match values: ${failedValues.join(', ')}`;\r\n }\r\n \r\n llmResponse.schema[fieldName].default = matchedValues.length > 0 ? matchedValues : null;\r\n llmResponse.schema[fieldName].fuzzyMatchConfidence = matchedValues.length / (matchedValues.length + failedValues.length);\r\n } else if (!isMultiselect) {\r\n // Handle single value (select, classification, thesaurus, position)\r\n const singleValue = Array.isArray(fieldValue) ? fieldValue[0] : fieldValue;\r\n const matchResult = fuzzyMatchOption(singleValue, fieldDef.options);\r\n \r\n if (matchResult) {\r\n llmResponse.schema[fieldName].default = matchResult.match;\r\n llmResponse.schema[fieldName].fuzzyMatchConfidence = matchResult.confidence;\r\n } else {\r\n fieldsWithErrors.push(fieldName);\r\n llmResponse.schema[fieldName].default = null;\r\n llmResponse.schema[fieldName].error = `Could not match \"${singleValue}\" to available options`;\r\n }\r\n }\r\n }\r\n }\r\n });\r\n \r\n if (fieldsWithErrors.length > 0) {\r\n llmResponse.matchingErrors = fieldsWithErrors;\r\n }\r\n }\r\n \r\n return llmResponse;\r\n }\r\n\r\n async fillFormFromJson(json: string, schema: any): Promise<any> {\r\n return this.fillForm(json, schema, false);\r\n }\r\n\r\n private async fillForm(data: string, schema: any, dataIsTranscription: boolean = true): Promise<any> {\r\n try {\r\n // Handle complex schema format with fields array\r\n if (schema?.fields || schema?.schema) {\r\n const finalSchema = schema?.fields || schema?.schema;\r\n 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.\r\n Rules:\r\n 1. Only extract values that can be confidently determined from the transcription\r\n 2. Respect field types (string, number, datetime, boolean, select)\r\n 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n 4. For date fields, use the following format: DD/MM/YYYY\r\n 5. For boolean fields, interpret yes/no, true/false, positive/negative responses\r\n 6. For select fields, use exact option values from the provided choices\r\n 7. Only include fields where relevant information is found\r\n 8. The current GMT datetime is ${(new Date() as any).toGMTString()}\r\n 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\r\n 10. IMPORTANT: Fields marked as \"readonly\" are presentation elements (headers, labels) that provide context but MUST NOT receive values\r\n 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\r\n 12. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them\r\n 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\"\r\n 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\r\n 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\r\n\r\n Respond with JSON in this exact format: {\"fields\": [{\"id\": \"field_id\", \"name\": \"field_name\", \"value\": \"extracted_value\"}]}`;\r\n\r\n let userPrompt = `\r\n Voice transcription: \"${data}\"\r\n\r\n Form fields:\r\n ${this.getOptimizeFieldsDescription(finalSchema)}\r\n\r\n Please extract values from the Transcription for these fields.\r\n `;\r\n\r\n //TODO\r\n\r\n // const userPrompt = (\r\n // dataIsTranscription \r\n // ?\r\n // `Voice transcription: \"${data}\"`\r\n // : \r\n // `Json datas: \"${JSON.stringify(data)}\"`\r\n // )+`\r\n\r\n // Form fields:\r\n // ${this.getOptimizeFieldsDescription(finalSchema)}\r\n\r\n // Please extract values from the ` + \r\n // (\r\n // dataIsTranscription \r\n // ?\r\n // `Transcription`\r\n // : \r\n // `Json generated file`\r\n // )+` for these fields.`;\r\n\r\n const endpoint = this.useProxy ? `${this.baseUrl}/complete` : `${this.baseUrl}/chat/completions`;\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n \r\n if (!this.useProxy) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(endpoint, {\r\n method: 'POST',\r\n headers: headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt }\r\n ],\r\n response_format: { type: 'json_object' },\r\n temperature: 1,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`LLM API failed: ${errorData.error?.message || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n const llmResponse = JSON.parse(result.choices[0].message.content);\r\n \r\n // Apply fuzzy matching for fields with many options\r\n return this.applyFuzzyMatching(llmResponse, schema);\r\n }\r\n\r\n // Handle simple schema format (backward compatibility)\r\n 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.\r\n Rules:\r\n 1. Only fill fields that can be confidently determined from the transcription\r\n 2. Respect field types (string, number, date, boolean, select)\r\n 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n 4. For date fields, use the following format: DD/MM/YYYY\r\n 5. For boolean fields, interpret yes/no, true/false, positive/negative responses\r\n 6. For select fields, match the closest option from the provided choices\r\n 7. Leave fields empty if no relevant information is found\r\n 8. Return the same schema structure with 'default' values filled\r\n 9. The current GMT datetime is ${(new Date() as any).toGMTString()}\r\n 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\r\n 11. IMPORTANT: Fields marked as \"readonly\" are presentation elements (headers, labels) that provide context but MUST NOT receive values\r\n 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\r\n 13. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them\r\n 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\"\r\n\r\n Respond with JSON in this exact format: {\"schema\": {...}}`;\r\n\r\n let userPrompt = `\r\n Voice transcription: \"${data}\"\r\n\r\n Form schema to fill:\r\n ${JSON.stringify(schema, null, 2)}\r\n\r\n Please fill the form fields based on the Transcription and return the schema with default values populated.\r\n `;\r\n\r\n // const userPrompt = (\r\n // dataIsTranscription \r\n // ?\r\n // `Voice transcription: \"${data}\"`\r\n // : \r\n // `Json datas: \"${JSON.stringify(data)}\"`\r\n // )+`\r\n\r\n // Form schema to fill:\r\n // ${JSON.stringify(schema, null, 2)}\r\n\r\n // Please fill the form fields based on the ` + \r\n // (\r\n // dataIsTranscription \r\n // ?\r\n // `Transcription`\r\n // : \r\n // `Json generated file`\r\n // )+` nd return the schema with default values populated.`;\r\n\r\n const endpoint = this.useProxy ? `${this.baseUrl}/complete` : `${this.baseUrl}/chat/completions`;\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n \r\n if (!this.useProxy) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(endpoint, {\r\n method: 'POST',\r\n headers: headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt }\r\n ],\r\n response_format: { type: 'json_object' },\r\n temperature: 1, // Low temperature for consistency\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`LLM API failed: ${errorData.error?.message || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n const filledSchema = JSON.parse(result.choices[0].message.content);\r\n \r\n // Validate that the response has the correct structure\r\n if (!filledSchema.schema) {\r\n throw new Error('Invalid response format from LLM service');\r\n }\r\n\r\n // Apply fuzzy matching for fields with many options\r\n return this.applyFuzzyMatching(filledSchema, schema);\r\n } catch (error) {\r\n throw new Error(`Form filling failed: ${error.message}`);\r\n }\r\n }\r\n}\r\n\r\nexport class LLMServiceFactory {\r\n static create(config: ServiceProviderConfig): LLMProvider {\r\n const provider = config.llm?.provider || 'openai';\r\n \r\n switch (provider) {\r\n case 'openai':\r\n return new OpenAILLMService(config.llm);\r\n default:\r\n throw new Error(`Unsupported LLM provider: ${provider}`);\r\n }\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"llm.service.js","sourceRoot":"","sources":["../../src/services/llm.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { LLMProvider, SpeechToTextProvider, ServiceProviderConfig, TranscriptionProvider, CompletionProvider } from '../types/service-providers.types';\r\nimport { FormSchema } from '../types/form-schema.types';\r\nimport { fuzzyMatchOption } from '../utils/fuzzy-match';\r\n\r\nexport class OpenAILLMService implements LLMProvider {\r\n private apiKey: string;\r\n private model: string;\r\n private baseUrl: string;\r\n private useProxy: boolean;\r\n private proxyUrl: string;\r\n\r\n constructor(config: ServiceProviderConfig['llm']) {\r\n this.useProxy = config?.useProxy || false;\r\n this.proxyUrl = config?.proxyUrl || 'http://localhost:8492';\r\n \r\n if (this.useProxy) {\r\n // Mode proxy: pas besoin d'API key\r\n this.apiKey = '';\r\n this.baseUrl = this.proxyUrl;\r\n } else {\r\n // Mode direct: API key requise\r\n this.apiKey = config?.apiKey || this.getEnvironmentVariable('OPENAI_API_KEY') || '';\r\n this.baseUrl = config?.baseUrl || 'https://api.openai.com/v1';\r\n \r\n if (!this.apiKey) {\r\n throw new Error('OpenAI API key is required for LLM service');\r\n }\r\n }\r\n \r\n this.model = config?.model || 'gpt-5-mini';\r\n }\r\n\r\n private getEnvironmentVariable(name: string): string | undefined {\r\n // In browser environment, we might get env vars through other means\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[name];\r\n }\r\n // Check if it's available as a global variable or through other means\r\n return (window as any)[name] || undefined;\r\n }\r\n\r\n private getOptimizeFieldsDescription(schema: any){\r\n if (Array.isArray(schema)) {\r\n return schema.map((field: any) => {\r\n let description = `- ID:${field.id || field.name} | ${field.name || field.title} ` +\r\n `(${field.type}` +\r\n `${field.required ? ', required' : ''}` +\r\n `${field.readonly ? ', readonly' : ''}` +\r\n `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default='+field.default : ''}` +\r\n `${field.min && field.min !== \"\" ? ', min='+field.min : ''}` +\r\n `${field.max && field.max !== \"\" ? ', max='+field.max : ''}` +\r\n `${field.pattern && field.pattern !== \"\" ? ', pattern='+field.pattern : ''}`;\r\n \r\n if (field.options && field.options.length > 0) {\r\n if (field.options.length < 50) {\r\n description += `) - options: ${field.options.join(', ')}`;\r\n } else {\r\n description += `, many options: ${field.options.length} total) - extract raw value, will be matched locally`;\r\n }\r\n } else {\r\n description += ')';\r\n }\r\n \r\n return description;\r\n }).join('\\n');\r\n }\r\n \r\n return Object.entries(schema).map(([fieldName, field]: [string, any]) => {\r\n let description = `- ID:${field.id || fieldName} | ${field.title || fieldName} ` +\r\n `(${field.type}` +\r\n `${field.required ? ', required' : ''}` +\r\n `${field.readonly ? ', readonly' : ''}` +\r\n `${field.default !== undefined && field.default !== null && field.default !== '' ? ', default='+field.default : ''}` +\r\n `${field.min && field.min !== \"\" ? ', min='+field.min : ''}` +\r\n `${field.max && field.max !== \"\" ? ', max='+field.max : ''}` +\r\n `${field.pattern && field.pattern !== \"\" ? ', pattern='+field.pattern : ''}`;\r\n \r\n if (field.options && field.options.length > 0) {\r\n if (field.options.length < 50) {\r\n description += `) - options: ${field.options.join(', ')}`;\r\n } else {\r\n description += `, many options: ${field.options.length} total) - extract raw value, will be matched locally`;\r\n }\r\n } else {\r\n description += ')';\r\n }\r\n \r\n return description;\r\n }).join('\\n');\r\n }\r\n\r\n async fillFormFromTranscription(transcription: string, schema: any): Promise<any> {\r\n return this.fillForm(transcription, schema, true);\r\n }\r\n\r\n private applyFuzzyMatching(llmResponse: any, schema: any): any {\r\n // Handle complex schema format with fields array\r\n if (schema?.fields || schema?.schema) {\r\n const finalSchema = schema?.fields || schema?.schema;\r\n const fieldsWithErrors: string[] = [];\r\n \r\n if (llmResponse.fields && Array.isArray(llmResponse.fields)) {\r\n llmResponse.fields = llmResponse.fields.map((field: any) => {\r\n // Find corresponding schema field\r\n const schemaField = Object.values(finalSchema).find((sf: any) => \r\n (sf.name === field.name || sf.title === field.name)\r\n ) as any;\r\n \r\n if (schemaField && schemaField.options && schemaField.options.length >= 50 && field.value) {\r\n const isMultiselect = schemaField.type === 'multiselect';\r\n \r\n // Handle multiselect (array of values)\r\n if (isMultiselect && Array.isArray(field.value)) {\r\n const matchedValues: string[] = [];\r\n const failedValues: string[] = [];\r\n \r\n field.value.forEach((val: string) => {\r\n const matchResult = fuzzyMatchOption(val, schemaField.options);\r\n if (matchResult) {\r\n matchedValues.push(matchResult.match);\r\n } else {\r\n failedValues.push(val);\r\n }\r\n });\r\n \r\n if (failedValues.length > 0) {\r\n fieldsWithErrors.push(field.name);\r\n field.error = `Could not match values: ${failedValues.join(', ')}`;\r\n }\r\n \r\n field.value = matchedValues.length > 0 ? matchedValues : null;\r\n field.fuzzyMatchConfidence = matchedValues.length / (matchedValues.length + failedValues.length);\r\n } else if (!isMultiselect) {\r\n // Handle single value (select, classification, thesaurus, position)\r\n const singleValue = Array.isArray(field.value) ? field.value[0] : field.value;\r\n const matchResult = fuzzyMatchOption(singleValue, schemaField.options);\r\n \r\n if (matchResult) {\r\n field.value = matchResult.match;\r\n field.fuzzyMatchConfidence = matchResult.confidence;\r\n } else {\r\n fieldsWithErrors.push(field.name);\r\n field.value = null;\r\n field.error = `Could not match \"${singleValue}\" to available options`;\r\n }\r\n }\r\n }\r\n \r\n return field;\r\n });\r\n }\r\n \r\n if (fieldsWithErrors.length > 0) {\r\n llmResponse.matchingErrors = fieldsWithErrors;\r\n }\r\n \r\n return llmResponse;\r\n }\r\n \r\n // Handle simple schema format\r\n if (schema?.schema) {\r\n const fieldsWithErrors: string[] = [];\r\n \r\n Object.entries(schema.schema).forEach(([fieldName, fieldDef]: [string, any]) => {\r\n if (fieldDef.options && fieldDef.options.length >= 50) {\r\n const fieldValue = llmResponse.schema?.[fieldName]?.default;\r\n \r\n if (fieldValue) {\r\n const isMultiselect = fieldDef.type === 'multiselect';\r\n \r\n // Handle multiselect (array of values)\r\n if (isMultiselect && Array.isArray(fieldValue)) {\r\n const matchedValues: string[] = [];\r\n const failedValues: string[] = [];\r\n \r\n fieldValue.forEach((val: string) => {\r\n const matchResult = fuzzyMatchOption(val, fieldDef.options);\r\n if (matchResult) {\r\n matchedValues.push(matchResult.match);\r\n } else {\r\n failedValues.push(val);\r\n }\r\n });\r\n \r\n if (failedValues.length > 0) {\r\n fieldsWithErrors.push(fieldName);\r\n llmResponse.schema[fieldName].error = `Could not match values: ${failedValues.join(', ')}`;\r\n }\r\n \r\n llmResponse.schema[fieldName].default = matchedValues.length > 0 ? matchedValues : null;\r\n llmResponse.schema[fieldName].fuzzyMatchConfidence = matchedValues.length / (matchedValues.length + failedValues.length);\r\n } else if (!isMultiselect) {\r\n // Handle single value (select, classification, thesaurus, position)\r\n const singleValue = Array.isArray(fieldValue) ? fieldValue[0] : fieldValue;\r\n const matchResult = fuzzyMatchOption(singleValue, fieldDef.options);\r\n \r\n if (matchResult) {\r\n llmResponse.schema[fieldName].default = matchResult.match;\r\n llmResponse.schema[fieldName].fuzzyMatchConfidence = matchResult.confidence;\r\n } else {\r\n fieldsWithErrors.push(fieldName);\r\n llmResponse.schema[fieldName].default = null;\r\n llmResponse.schema[fieldName].error = `Could not match \"${singleValue}\" to available options`;\r\n }\r\n }\r\n }\r\n }\r\n });\r\n \r\n if (fieldsWithErrors.length > 0) {\r\n llmResponse.matchingErrors = fieldsWithErrors;\r\n }\r\n }\r\n \r\n return llmResponse;\r\n }\r\n\r\n async fillFormFromJson(json: string, schema: any): Promise<any> {\r\n return this.fillForm(json, schema, false);\r\n }\r\n\r\n private async fillForm(data: string, schema: any, dataIsTranscription: boolean = true): Promise<any> {\r\n try {\r\n // Handle complex schema format with fields array\r\n if (schema?.fields || schema?.schema) {\r\n const finalSchema = schema?.fields || schema?.schema;\r\n 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.\r\n Rules:\r\n 1. Only extract values that can be confidently determined from the transcription\r\n 2. Respect field types (string, number, datetime, boolean, select)\r\n 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n 4. For date fields, use the following format: DD/MM/YYYY\r\n 5. For boolean fields, interpret yes/no, true/false, positive/negative responses\r\n 6. For select fields, use exact option values from the provided choices\r\n 7. Only include fields where relevant information is found\r\n 8. The current GMT datetime is ${(new Date() as any).toGMTString()}\r\n 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\r\n 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\r\n 10. IMPORTANT: Fields marked as \"readonly\" are presentation elements (headers, labels) that provide context but MUST NOT receive values\r\n 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\r\n 12. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them\r\n 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\r\n 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\r\n 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\r\n 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\r\n\r\n Respond with JSON in this exact format: {\"fields\": [{\"id\": \"field_id\", \"name\": \"field_name\", \"value\": \"extracted_value\"}]}`;\r\n\r\n let userPrompt = `\r\n Voice transcription: \"${data}\"\r\n\r\n Form fields:\r\n ${this.getOptimizeFieldsDescription(finalSchema)}\r\n\r\n Please extract values from the Transcription for these fields.\r\n `;\r\n\r\n //TODO\r\n\r\n // const userPrompt = (\r\n // dataIsTranscription \r\n // ?\r\n // `Voice transcription: \"${data}\"`\r\n // : \r\n // `Json datas: \"${JSON.stringify(data)}\"`\r\n // )+`\r\n\r\n // Form fields:\r\n // ${this.getOptimizeFieldsDescription(finalSchema)}\r\n\r\n // Please extract values from the ` + \r\n // (\r\n // dataIsTranscription \r\n // ?\r\n // `Transcription`\r\n // : \r\n // `Json generated file`\r\n // )+` for these fields.`;\r\n\r\n const endpoint = this.useProxy ? `${this.baseUrl}/complete` : `${this.baseUrl}/chat/completions`;\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n \r\n if (!this.useProxy) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(endpoint, {\r\n method: 'POST',\r\n headers: headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt }\r\n ],\r\n response_format: { type: 'json_object' },\r\n temperature: 1,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`LLM API failed: ${errorData.error?.message || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n const llmResponse = JSON.parse(result.choices[0].message.content);\r\n \r\n // Apply fuzzy matching for fields with many options\r\n return this.applyFuzzyMatching(llmResponse, schema);\r\n }\r\n\r\n // Handle simple schema format (backward compatibility)\r\n 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.\r\n Rules:\r\n 1. Only fill fields that can be confidently determined from the transcription\r\n 2. Respect field types (string, number, date, boolean, select)\r\n 3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n 4. For date fields, use the following format: DD/MM/YYYY\r\n 5. For boolean fields, interpret yes/no, true/false, positive/negative responses\r\n 6. For select fields, match the closest option from the provided choices\r\n 7. Leave fields empty if no relevant information is found\r\n 8. Return the same schema structure with 'default' values filled\r\n 9. The current GMT datetime is ${(new Date() as any).toGMTString()}\r\n 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\r\n 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\r\n 11. IMPORTANT: Fields marked as \"readonly\" are presentation elements (headers, labels) that provide context but MUST NOT receive values\r\n 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\r\n 13. Use readonly fields as contextual hints to understand form structure and field grouping, but never fill them\r\n 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\r\n 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\r\n\r\n Respond with JSON in this exact format: {\"schema\": {...}}`;\r\n\r\n let userPrompt = `\r\n Voice transcription: \"${data}\"\r\n\r\n Form schema to fill:\r\n ${JSON.stringify(schema, null, 2)}\r\n\r\n Please fill the form fields based on the Transcription and return the schema with default values populated.\r\n `;\r\n\r\n // const userPrompt = (\r\n // dataIsTranscription \r\n // ?\r\n // `Voice transcription: \"${data}\"`\r\n // : \r\n // `Json datas: \"${JSON.stringify(data)}\"`\r\n // )+`\r\n\r\n // Form schema to fill:\r\n // ${JSON.stringify(schema, null, 2)}\r\n\r\n // Please fill the form fields based on the ` + \r\n // (\r\n // dataIsTranscription \r\n // ?\r\n // `Transcription`\r\n // : \r\n // `Json generated file`\r\n // )+` nd return the schema with default values populated.`;\r\n\r\n const endpoint = this.useProxy ? `${this.baseUrl}/complete` : `${this.baseUrl}/chat/completions`;\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n \r\n if (!this.useProxy) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const response = await fetch(endpoint, {\r\n method: 'POST',\r\n headers: headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [\r\n { role: 'system', content: systemPrompt },\r\n { role: 'user', content: userPrompt }\r\n ],\r\n response_format: { type: 'json_object' },\r\n temperature: 1, // Low temperature for consistency\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`LLM API failed: ${errorData.error?.message || response.statusText}`);\r\n }\r\n\r\n const result = await response.json();\r\n const filledSchema = JSON.parse(result.choices[0].message.content);\r\n \r\n // Validate that the response has the correct structure\r\n if (!filledSchema.schema) {\r\n throw new Error('Invalid response format from LLM service');\r\n }\r\n\r\n // Apply fuzzy matching for fields with many options\r\n return this.applyFuzzyMatching(filledSchema, schema);\r\n } catch (error) {\r\n throw new Error(`Form filling failed: ${error.message}`);\r\n }\r\n }\r\n}\r\n\r\nexport class AnthropicLLMService implements LLMProvider {\r\n private apiKey: string;\r\n private model: string;\r\n private useProxy: boolean;\r\n private proxyUrl: string;\r\n\r\n constructor(config: ServiceProviderConfig['llm']) {\r\n this.useProxy = config?.useProxy || false;\r\n this.proxyUrl = config?.proxyUrl || 'http://localhost:8492';\r\n this.model = config?.model || 'claude-sonnet-4.5';\r\n this.apiKey = this.useProxy ? '' : (config?.apiKey || '');\r\n if (!this.useProxy && !this.apiKey) {\r\n throw new Error('Anthropic API key is required');\r\n }\r\n }\r\n\r\n async fillFormFromTranscription(transcription: string, schema: any): Promise<any> {\r\n return this.fillForm(transcription, schema);\r\n }\r\n\r\n async fillFormFromJson(json: string, schema: any): Promise<any> {\r\n return this.fillForm(json, schema);\r\n }\r\n\r\n private async fillForm(data: string, schema: any): Promise<any> {\r\n const endpoint = this.useProxy ? `${this.proxyUrl}/complete-anthropic` : 'https://api.anthropic.com/v1/messages';\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n if (!this.useProxy) {\r\n headers['x-api-key'] = this.apiKey;\r\n headers['anthropic-version'] = '2023-06-01';\r\n }\r\n\r\n const finalSchema = schema?.fields || schema?.schema || schema;\r\n const systemPrompt = this.buildSystemPrompt();\r\n const userPrompt = `Data: \"${data}\"\r\n\r\nForm fields:\r\n${JSON.stringify(finalSchema, null, 2)}\r\n\r\nRespond with JSON: {\"fields\": [{\"id\": \"field_id\", \"name\": \"field_name\", \"value\": \"extracted_value\"}]}`;\r\n\r\n const body = this.useProxy\r\n ? { model: this.model, messages: [{ role: 'user', content: userPrompt }], system: systemPrompt }\r\n : { model: this.model, max_tokens: 4096, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] };\r\n\r\n const response = await fetch(endpoint, { method: 'POST', headers, body: JSON.stringify(body) });\r\n if (!response.ok) {\r\n const err = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`Anthropic API failed: ${err.error?.message || response.statusText}`);\r\n }\r\n const result = await response.json();\r\n const content = this.useProxy ? result.choices[0].message.content : result.content[0].text;\r\n return JSON.parse(content);\r\n }\r\n\r\n private buildSystemPrompt(): string {\r\n return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.\r\nRules:\r\n1. Only extract values that can be confidently determined\r\n2. Respect field types (string, number, datetime, boolean, select)\r\n3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n4. For date fields, use DD/MM/YYYY format\r\n5. For select fields, use exact option values from the provided choices\r\n6. Leave fields empty if no relevant information is found\r\n7. Fields marked readonly MUST NOT receive values\r\n8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY\r\n9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty\r\n10. UNKNOWN VALUES: if the user says \"Unknown\" and it exists as an option, select it; otherwise leave empty\r\nRespond ONLY with valid JSON.`;\r\n }\r\n}\r\n\r\nexport class MistralLLMService implements LLMProvider {\r\n private apiKey: string;\r\n private model: string;\r\n private useProxy: boolean;\r\n private proxyUrl: string;\r\n\r\n constructor(config: ServiceProviderConfig['llm']) {\r\n this.useProxy = config?.useProxy || false;\r\n this.proxyUrl = config?.proxyUrl || 'http://localhost:8492';\r\n this.model = config?.model || 'mistral-medium-latest';\r\n this.apiKey = this.useProxy ? '' : (config?.apiKey || '');\r\n if (!this.useProxy && !this.apiKey) {\r\n throw new Error('Mistral API key is required');\r\n }\r\n }\r\n\r\n async fillFormFromTranscription(transcription: string, schema: any): Promise<any> {\r\n return this.fillForm(transcription, schema);\r\n }\r\n\r\n async fillFormFromJson(json: string, schema: any): Promise<any> {\r\n return this.fillForm(json, schema);\r\n }\r\n\r\n private async fillForm(data: string, schema: any): Promise<any> {\r\n const endpoint = this.useProxy ? `${this.proxyUrl}/complete-mistral` : 'https://api.mistral.ai/v1/chat/completions';\r\n const headers: any = { 'Content-Type': 'application/json' };\r\n if (!this.useProxy) {\r\n headers['Authorization'] = `Bearer ${this.apiKey}`;\r\n }\r\n\r\n const finalSchema = schema?.fields || schema?.schema || schema;\r\n const systemPrompt = this.buildSystemPrompt();\r\n const userPrompt = `Data: \"${data}\"\r\n\r\nForm fields:\r\n${JSON.stringify(finalSchema, null, 2)}\r\n\r\nRespond with JSON: {\"fields\": [{\"id\": \"field_id\", \"name\": \"field_name\", \"value\": \"extracted_value\"}]}`;\r\n\r\n const response = await fetch(endpoint, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify({\r\n model: this.model,\r\n messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt }],\r\n response_format: { type: 'json_object' },\r\n }),\r\n });\r\n if (!response.ok) {\r\n const err = await response.json().catch(() => ({ error: 'Unknown error' }));\r\n throw new Error(`Mistral API failed: ${err.error?.message || response.statusText}`);\r\n }\r\n const result = await response.json();\r\n return JSON.parse(result.choices[0].message.content);\r\n }\r\n\r\n private buildSystemPrompt(): string {\r\n return `You are an expert form-filling assistant. Extract values from the input data and fill form fields.\r\nRules:\r\n1. Only extract values that can be confidently determined\r\n2. Respect field types (string, number, datetime, boolean, select)\r\n3. For datetime fields, use ISO format (YYYY-MM-DDTHH:MM)\r\n4. For date fields, use DD/MM/YYYY format\r\n5. For select fields, use exact option values from the provided choices\r\n6. Leave fields empty if no relevant information is found\r\n7. Fields marked readonly MUST NOT receive values\r\n8. DATE/DATETIME OUT OF RANGE: if a date falls outside min/max constraints, leave the field EMPTY\r\n9. PATTERN VALIDATION: if a field has a pattern, the value MUST match it exactly, otherwise leave empty\r\n10. UNKNOWN VALUES: if the user says \"Unknown\" and it exists as an option, select it; otherwise leave empty\r\nRespond ONLY with valid JSON.`;\r\n }\r\n}\r\n\r\nexport class LLMServiceFactory {\r\n static create(config: ServiceProviderConfig): LLMProvider {\r\n const provider = config.llm?.provider || 'openai';\r\n switch (provider) {\r\n case 'openai': return new OpenAILLMService(config.llm);\r\n case 'anthropic': return new AnthropicLLMService(config.llm);\r\n case 'mistral': return new MistralLLMService(config.llm);\r\n default: throw new Error(`Unsupported LLM provider: ${provider}`);\r\n }\r\n }\r\n}\r\n"]}