iatoolkit 0.7.10__py3-none-any.whl → 0.7.11__py3-none-any.whl

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.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (126) hide show
  1. iatoolkit/__init__.py +18 -14
  2. iatoolkit/base_company.py +5 -5
  3. iatoolkit/cli_commands.py +5 -3
  4. {common → iatoolkit/common}/auth.py +3 -3
  5. {common → iatoolkit/common}/routes.py +19 -19
  6. {common → iatoolkit/common}/util.py +2 -2
  7. iatoolkit/iatoolkit.py +37 -38
  8. {infra → iatoolkit/infra}/call_service.py +1 -1
  9. {infra → iatoolkit/infra}/connectors/file_connector_factory.py +5 -5
  10. {infra → iatoolkit/infra}/connectors/google_cloud_storage_connector.py +1 -1
  11. {infra → iatoolkit/infra}/connectors/google_drive_connector.py +1 -1
  12. {infra → iatoolkit/infra}/connectors/local_file_connector.py +2 -2
  13. {infra → iatoolkit/infra}/connectors/s3_connector.py +1 -1
  14. {infra → iatoolkit/infra}/gemini_adapter.py +2 -2
  15. {infra → iatoolkit/infra}/google_chat_app.py +1 -1
  16. {infra → iatoolkit/infra}/llm_client.py +7 -7
  17. {infra → iatoolkit/infra}/llm_proxy.py +6 -6
  18. {infra → iatoolkit/infra}/mail_app.py +1 -1
  19. {infra → iatoolkit/infra}/openai_adapter.py +2 -2
  20. {repositories → iatoolkit/repositories}/database_manager.py +1 -1
  21. {repositories → iatoolkit/repositories}/document_repo.py +3 -3
  22. {repositories → iatoolkit/repositories}/llm_query_repo.py +2 -2
  23. {repositories → iatoolkit/repositories}/profile_repo.py +2 -2
  24. {repositories → iatoolkit/repositories}/tasks_repo.py +2 -2
  25. {repositories → iatoolkit/repositories}/vs_repo.py +3 -3
  26. {services → iatoolkit/services}/benchmark_service.py +3 -3
  27. {services → iatoolkit/services}/dispatcher_service.py +9 -8
  28. {services → iatoolkit/services}/document_service.py +1 -1
  29. {services → iatoolkit/services}/excel_service.py +2 -2
  30. {services → iatoolkit/services}/file_processor_service.py +2 -2
  31. {services → iatoolkit/services}/history_service.py +4 -3
  32. {services → iatoolkit/services}/load_documents_service.py +11 -10
  33. {services → iatoolkit/services}/mail_service.py +2 -2
  34. {services → iatoolkit/services}/profile_service.py +6 -6
  35. {services → iatoolkit/services}/prompt_manager_service.py +5 -4
  36. {services → iatoolkit/services}/query_service.py +12 -11
  37. {services → iatoolkit/services}/search_service.py +2 -2
  38. {services → iatoolkit/services}/sql_service.py +4 -3
  39. {services → iatoolkit/services}/tasks_service.py +6 -6
  40. {services → iatoolkit/services}/user_feedback_service.py +3 -3
  41. {services → iatoolkit/services}/user_session_context_service.py +1 -1
  42. iatoolkit/static/images/arrow_up.png +0 -0
  43. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  44. iatoolkit/static/images/logo_clinica.png +0 -0
  45. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  46. iatoolkit/static/images/logo_maxxa.png +0 -0
  47. iatoolkit/static/images/logo_notaria.png +0 -0
  48. iatoolkit/static/images/logo_tarjeta.png +0 -0
  49. iatoolkit/static/images/logo_umayor.png +0 -0
  50. iatoolkit/static/images/upload.png +0 -0
  51. iatoolkit/static/js/chat_feedback.js +115 -0
  52. iatoolkit/static/js/chat_filepond.js +85 -0
  53. iatoolkit/static/js/chat_history.js +117 -0
  54. iatoolkit/static/js/chat_main.js +436 -0
  55. iatoolkit/static/styles/chat_iatoolkit.css +701 -0
  56. iatoolkit/static/styles/chat_info.css +53 -0
  57. iatoolkit/static/styles/chat_modal.css +136 -0
  58. iatoolkit/static/styles/llm_output.css +115 -0
  59. iatoolkit/static/temp/024f44a9-cc7e-4bde-9a3c-11903a8f5d3d.xlsx +0 -0
  60. iatoolkit/static/temp/0b97768e-79e8-43ec-b17e-ba3137f94e93.xlsx +0 -0
  61. iatoolkit/static/temp/202883ee-763e-4b40-9bb6-bfacfc5e65fe.xlsx +0 -0
  62. iatoolkit/static/temp/28287491-08b7-4863-a2a3-49fcb64a0906.xlsx +0 -0
  63. iatoolkit/static/temp/36780cac-7a46-4db4-ac98-7338a51aaf52.xlsx +0 -0
  64. iatoolkit/static/temp/5c1b66f6-d58f-4684-8a7a-df3bb1a35eaa.xlsx +0 -0
  65. iatoolkit/static/temp/5d5a3500-ec57-4e07-a554-8799a906d1ab.xlsx +0 -0
  66. iatoolkit/static/temp/65887e40-cf64-49aa-8d1f-651cb0f8cdf0.xlsx +0 -0
  67. iatoolkit/static/temp/6fa64b13-e8e5-40ad-8257-00fd1682dad8.xlsx +0 -0
  68. iatoolkit/static/temp/7ab7071f-ad9b-49e6-8e63-f08410625ce7.xlsx +0 -0
  69. iatoolkit/static/temp/824346d9-d54d-40c6-b5c2-9e1d84d0ae90.xlsx +0 -0
  70. iatoolkit/static/temp/9ca80c3d-d196-4dfc-8179-582584fae04c.xlsx +0 -0
  71. iatoolkit/static/temp/a34cb8a6-85fb-4ea8-aabe-889967cd83b5.xlsx +0 -0
  72. iatoolkit/static/temp/a7fc9c13-c509-4499-b4be-23bfa57cac31.xlsx +0 -0
  73. iatoolkit/static/temp/b83084f5-fe54-4580-885e-412b4388cbda.xlsx +0 -0
  74. iatoolkit/static/temp/c17c6864-34e1-448f-b0e9-380354256ea9.xlsx +0 -0
  75. iatoolkit/static/temp/customer_clusters.parquet +0 -0
  76. iatoolkit/static/temp/customer_clusters.xlsx +0 -0
  77. iatoolkit/static/temp/d1af98b8-18a9-4b94-b9bc-607d19a87d0d.xlsx +0 -0
  78. iatoolkit/templates/about.html +13 -0
  79. iatoolkit/templates/base.html +45 -0
  80. iatoolkit/templates/change_password.html +45 -0
  81. iatoolkit/templates/chat.html +180 -0
  82. iatoolkit/templates/chat_modals.html +115 -0
  83. iatoolkit/templates/error.html +15 -0
  84. iatoolkit/templates/forgot_password.html +33 -0
  85. iatoolkit/templates/header.html +31 -0
  86. iatoolkit/templates/home.html +201 -0
  87. iatoolkit/templates/login.html +43 -0
  88. iatoolkit/templates/signup.html +78 -0
  89. iatoolkit/templates/test.html +9 -0
  90. {views → iatoolkit/views}/change_password_view.py +1 -1
  91. {views → iatoolkit/views}/chat_token_request_view.py +2 -2
  92. {views → iatoolkit/views}/chat_view.py +3 -3
  93. {views → iatoolkit/views}/download_file_view.py +3 -3
  94. {views → iatoolkit/views}/external_chat_login_view.py +5 -5
  95. {views → iatoolkit/views}/external_login_view.py +2 -2
  96. {views → iatoolkit/views}/file_store_view.py +2 -2
  97. {views → iatoolkit/views}/forgot_password_view.py +1 -1
  98. {views → iatoolkit/views}/history_view.py +2 -2
  99. {views → iatoolkit/views}/home_view.py +1 -1
  100. {views → iatoolkit/views}/llmquery_view.py +2 -2
  101. {views → iatoolkit/views}/login_view.py +1 -1
  102. {views → iatoolkit/views}/prompt_view.py +2 -2
  103. {views → iatoolkit/views}/signup_view.py +1 -1
  104. {views → iatoolkit/views}/tasks_review_view.py +2 -2
  105. {views → iatoolkit/views}/tasks_view.py +2 -2
  106. {views → iatoolkit/views}/user_feedback_view.py +2 -2
  107. {views → iatoolkit/views}/verify_user_view.py +1 -1
  108. {iatoolkit-0.7.10.dist-info → iatoolkit-0.7.11.dist-info}/METADATA +1 -1
  109. iatoolkit-0.7.11.dist-info/RECORD +128 -0
  110. iatoolkit-0.7.11.dist-info/top_level.txt +1 -0
  111. iatoolkit-0.7.10.dist-info/RECORD +0 -80
  112. iatoolkit-0.7.10.dist-info/top_level.txt +0 -6
  113. {common → iatoolkit/common}/__init__.py +0 -0
  114. {common → iatoolkit/common}/exceptions.py +0 -0
  115. {common → iatoolkit/common}/session_manager.py +0 -0
  116. {infra → iatoolkit/infra}/__init__.py +0 -0
  117. {infra → iatoolkit/infra}/connectors/__init__.py +0 -0
  118. {infra → iatoolkit/infra}/connectors/file_connector.py +0 -0
  119. {infra → iatoolkit/infra}/llm_response.py +0 -0
  120. {infra → iatoolkit/infra}/redis_session_manager.py +0 -0
  121. {repositories → iatoolkit/repositories}/__init__.py +0 -0
  122. {repositories → iatoolkit/repositories}/models.py +0 -0
  123. {services → iatoolkit/services}/__init__.py +0 -0
  124. {services → iatoolkit/services}/jwt_service.py +0 -0
  125. {views → iatoolkit/views}/__init__.py +0 -0
  126. {iatoolkit-0.7.10.dist-info → iatoolkit-0.7.11.dist-info}/WHEEL +0 -0
@@ -0,0 +1,436 @@
1
+ // Global variables for request management
2
+ let currentAbortController = null;
3
+ let isRequestInProgress = false;
4
+
5
+ let selectedPrompt = null; // Will hold a lightweight prompt object
6
+
7
+ $(document).ready(function () {
8
+ // --- MAIN EVENT HANDLERS ---
9
+ $('#send-button').on('click', handleChatMessage);
10
+ $('#stop-button').on('click', abortCurrentRequest);
11
+
12
+ // --- PROMPT ASSISTANT FUNCTIONALITY ---
13
+ $('.input-area').on('click', '.dropdown-menu a.dropdown-item', function (event) {
14
+ event.preventDefault();
15
+ const promptData = $(this).data();
16
+
17
+ const promptObject = {
18
+ prompt: promptData.promptName,
19
+ description: promptData.promptDescription,
20
+ custom_fields: typeof promptData.customFields === 'string' ? JSON.parse(promptData.customFields) : promptData.customFields
21
+ };
22
+
23
+ selectPrompt(promptObject);
24
+ });
25
+
26
+ // Handles the 'clear' button for the prompt selector
27
+ $('#clear-selection-button').on('click', function() {
28
+ resetPromptSelection();
29
+ updateSendButtonState();
30
+ });
31
+
32
+ // --- TEXTAREA FUNCTIONALITY ---
33
+ const questionTextarea = $('#question');
34
+
35
+ // Handles Enter key press to send a message
36
+ questionTextarea.on('keypress', function (event) {
37
+ if (event.key === 'Enter' && !event.shiftKey) {
38
+ event.preventDefault();
39
+ handleChatMessage();
40
+ }
41
+ });
42
+
43
+ // Handles auto-resizing and enables the send button on input
44
+ questionTextarea.on('input', function () {
45
+ autoResizeTextarea(this);
46
+ // If the user types, it overrides any prompt selection
47
+ if (selectedPrompt) {
48
+ resetPromptSelection();
49
+ }
50
+ updateSendButtonState();
51
+ });
52
+
53
+ // Set the initial disabled state of the send button
54
+ updateSendButtonState();
55
+ });
56
+
57
+
58
+ /**
59
+ * Handles the selection of a prompt from the dropdown.
60
+ * @param {object} prompt The prompt object read from data attributes.
61
+ */
62
+ function selectPrompt(prompt) {
63
+ selectedPrompt = prompt;
64
+
65
+ // Update the dropdown button to show the selected prompt's description
66
+ $('#prompt-select-button').text(prompt.description).addClass('item-selected');
67
+ $('#clear-selection-button').show();
68
+
69
+ // Clear the main textarea, as we are now in "prompt mode"
70
+ $('#question').val('');
71
+ autoResizeTextarea($('#question')[0]); // Reset height after clearing
72
+
73
+ // Store values in hidden fields for backward compatibility or other uses
74
+ $('#prompt-select-value').val(prompt.prompt);
75
+ $('#prompt-select-description').val(prompt.description);
76
+
77
+ // Render the dynamic input fields required by the selected prompt
78
+ renderDynamicInputs(prompt.custom_fields || []);
79
+ updateSendButtonState();
80
+ }
81
+
82
+ /**
83
+ * Resets the prompt selection and clears associated UI elements.
84
+ */
85
+ function resetPromptSelection() {
86
+ selectedPrompt = null;
87
+
88
+ $('#prompt-select-button').text('Prompts disponibles ....').removeClass('item-selected');
89
+ $('#clear-selection-button').hide();
90
+ $('#prompt-select-value').val('');
91
+ $('#prompt-select-description').val('');
92
+
93
+ // Clear any dynamically generated input fields
94
+ $('#dynamic-inputs-container').empty();
95
+ }
96
+
97
+ /**
98
+ * Renders the custom input fields for the selected prompt.
99
+ * @param {Array<object>} fields The array of custom field configurations.
100
+ */
101
+ function renderDynamicInputs(fields) {
102
+ const container = $('#dynamic-inputs-container');
103
+ container.empty();
104
+
105
+ const row = $('<div class="row g-2"></div>');
106
+ fields.forEach(field => {
107
+ const colDiv = $('<div class="col-md"></div>');
108
+ const formFloating = $('<div class="form-floating"></div>');
109
+ const input = $(`<input type="${field.type || 'text'}" class="form-control form-control-soft" id="${field.data_key}-id" ">`);
110
+ const label = $(`<label for="${field.data_key}-id">${field.label}</label>`);
111
+
112
+ formFloating.append(input, label);
113
+ colDiv.append(formFloating);
114
+ row.append(colDiv);
115
+ });
116
+
117
+ container.append(row);
118
+ }
119
+
120
+
121
+ /**
122
+ * Main function to handle sending a chat message.
123
+ */
124
+ const handleChatMessage = async function () {
125
+ if (isRequestInProgress || $('#send-button').hasClass('disabled')) return;
126
+
127
+ const question = $('#question').val().trim();
128
+ const promptName = selectedPrompt ? selectedPrompt.prompt : null;
129
+
130
+ let displayMessage = question;
131
+ let isEditable = true;
132
+ const clientData = {};
133
+
134
+ if (selectedPrompt) {
135
+ displayMessage = selectedPrompt.description;
136
+ isEditable = false; // Prompts are not editable
137
+
138
+ (selectedPrompt.custom_fields || []).forEach(field => {
139
+ const value = $('#' + field.data_key + '-id').val().trim();
140
+ if (value) {
141
+ clientData[field.data_key] = value;
142
+ }
143
+ });
144
+
145
+ // Append the collected parameter values to the display message
146
+ const paramsString = Object.values(clientData).join(', ');
147
+ if (paramsString) {
148
+ displayMessage += `: ${paramsString}`;
149
+ }
150
+ }
151
+
152
+ // Si no hay pregunta libre Y no se ha seleccionado un prompt, no hacer nada.
153
+ if (!displayMessage) return;
154
+
155
+ displayUserMessage(displayMessage, isEditable, question);
156
+ showSpinner();
157
+ toggleSendStopButtons(true);
158
+
159
+ resetAllInputs();
160
+
161
+ const files = window.filePond.getFiles();
162
+ const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
163
+
164
+ // Prepare data payload
165
+ const data = {
166
+ question: question,
167
+ prompt_name: promptName,
168
+ client_data: clientData,
169
+ files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
170
+ external_user_id: window.externalUserId
171
+ };
172
+
173
+ try {
174
+ const responseData = await callLLMAPI("/llm_query", data, "POST");
175
+ if (responseData && responseData.answer) {
176
+ const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
177
+ displayBotMessage(answerSection);
178
+ }
179
+ } catch (error) {
180
+ console.error("Error in handleChatMessage:", error);
181
+ // Implement error display logic as needed
182
+ } finally {
183
+ hideSpinner();
184
+ toggleSendStopButtons(false);
185
+ updateSendButtonState();
186
+ window.filePond.removeFiles();
187
+ }
188
+ };
189
+
190
+ /**
191
+ * Resets all inputs to their initial state.
192
+ */
193
+ function resetAllInputs() {
194
+ resetPromptSelection();
195
+ $('#question').val('');
196
+ autoResizeTextarea($('#question')[0]);
197
+
198
+ const promptCollapseEl = document.getElementById('prompt-assistant-collapse');
199
+ const promptCollapse = bootstrap.Collapse.getInstance(promptCollapseEl);
200
+ if (promptCollapse) {
201
+ promptCollapse.hide();
202
+ }
203
+
204
+ updateSendButtonState();
205
+ }
206
+
207
+ /**
208
+ * Enables or disables the send button based on whether there's content
209
+ * in the textarea or a prompt has been selected.
210
+ */
211
+ function updateSendButtonState() {
212
+ const question = $('#question').val().trim();
213
+ const isPromptSelected = selectedPrompt !== null;
214
+
215
+ if (isPromptSelected || question) {
216
+ $('#send-button').removeClass('disabled');
217
+ } else {
218
+ $('#send-button').addClass('disabled');
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Auto-resizes the textarea to fit its content.
224
+ */
225
+ function autoResizeTextarea(element) {
226
+ element.style.height = 'auto';
227
+ element.style.height = (element.scrollHeight) + 'px';
228
+ }
229
+
230
+ /**
231
+ * Toggles the main action button between 'Send' and 'Stop'.
232
+ * @param {boolean} showStop - If true, shows the Stop button. Otherwise, shows the Send button.
233
+ */
234
+ const toggleSendStopButtons = function (showStop) {
235
+ $('#send-button-container').toggle(!showStop);
236
+ $('#stop-button-container').toggle(showStop);
237
+ };
238
+
239
+ /**
240
+ * Resets the prompt selector to its default state.
241
+ */
242
+ function resetPromptSelect() {
243
+ $('#prompt-select-button').text('Prompts disponibles ....').removeClass('item-selected');
244
+ $('#prompt-select-value').val('');
245
+ $('#prompt-select-description').val('');
246
+ $('#clear-selection-button').hide();
247
+ }
248
+
249
+ /**
250
+ * Resets the company-specific data input field.
251
+ */
252
+ function resetSpecificDataInput() {
253
+ if (specificDataConfig && specificDataConfig.enabled) {
254
+ const input = $('#' + specificDataConfig.id);
255
+ input.val('').removeClass('has-content');
256
+ $('#clear-' + specificDataConfig.id + '-button').hide();
257
+ }
258
+ }
259
+
260
+
261
+ /**
262
+ * Generic function to make API calls to the backend.
263
+ * @param {string} apiPath - The API endpoint path.
264
+ * @param {object} data - The data payload to send.
265
+ * @param {string} method - The HTTP method (e.g., 'POST').
266
+ * @param {number} timeoutMs - Timeout in milliseconds.
267
+ * @returns {Promise<object|null>} The response data or null on error.
268
+ */
269
+ const callLLMAPI = async function(apiPath, data, method, timeoutMs = 500000) {
270
+ const url = `${window.iatoolkit_base_url}/${window.companyShortName}${apiPath}`;
271
+
272
+ const headers = {"Content-Type": "application/json"};
273
+ if (window.sessionJWT) {
274
+ headers['X-Chat-Token'] = window.sessionJWT;
275
+ }
276
+
277
+ const controller = new AbortController();
278
+ currentAbortController = controller;
279
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
280
+
281
+ try {
282
+ const response = await fetch(url, {
283
+ method: method,
284
+ headers: headers,
285
+ body: JSON.stringify(data),
286
+ signal: controller.signal,
287
+ credentials: 'include'
288
+ });
289
+ clearTimeout(timeoutId);
290
+
291
+ if (!response.ok) {
292
+ const errorData = await response.json();
293
+ const endpointError = $('<div>').addClass('error-section').append(`<p>${errorData.error_message || 'Unknown server error'}</p>`);
294
+ displayBotMessage(endpointError);
295
+ return null;
296
+ }
297
+ return await response.json();
298
+ } catch (error) {
299
+ clearTimeout(timeoutId);
300
+ if (error.name === 'AbortError') {
301
+ throw error; // Re-throw to be handled by handleChatMessage
302
+ } else {
303
+ const commError = $('<div>').addClass('error-section').append(`<p>Connection error: ${error.message}</p>`);
304
+ displayBotMessage(commError);
305
+ }
306
+ return null;
307
+ }
308
+ };
309
+
310
+
311
+ /**
312
+ * Displays the user's message in the chat container.
313
+ * @param {string} message - The full message string to display.
314
+ * @param {boolean} isEditable - Determines if the edit icon should be shown.
315
+ * @param {string} originalQuestion - The original text to put back in the textarea for editing.
316
+ */
317
+ const displayUserMessage = function(message, isEditable, originalQuestion) {
318
+ const chatContainer = $('#chat-container');
319
+ const userMessage = $('<div>').addClass('message shadow-sm');
320
+ const messageText = $('<span>').text(message);
321
+
322
+ userMessage.append(messageText);
323
+
324
+ if (isEditable) {
325
+ const editIcon = $('<i>').addClass('bi bi-pencil-fill edit-icon').attr('title', 'Edit query').on('click', function () {
326
+ $('#question').val(originalQuestion).focus();
327
+ autoResizeTextarea($('#question')[0]);
328
+ updateSendButtonState();
329
+ });
330
+ userMessage.append(editIcon);
331
+ }
332
+ chatContainer.append(userMessage);
333
+ chatContainer.scrollTop(chatContainer[0].scrollHeight);
334
+ };
335
+
336
+ /**
337
+ * Appends a message from the bot to the chat container.
338
+ * @param {jQuery} section - The jQuery object to append.
339
+ */
340
+ function displayBotMessage(section) {
341
+ const chatContainer = $('#chat-container');
342
+ chatContainer.append(section);
343
+ chatContainer.scrollTop(chatContainer[0].scrollHeight);
344
+ }
345
+
346
+ /**
347
+ * Aborts the current in-progress API request.
348
+ */
349
+ const abortCurrentRequest = function () {
350
+ if (currentAbortController && isRequestInProgress) {
351
+ window.isManualAbort = true;
352
+ currentAbortController.abort();
353
+ }
354
+ };
355
+
356
+ /**
357
+ * Shows the loading spinner in the chat.
358
+ */
359
+ const showSpinner = function () {
360
+ if ($('#spinner').length) return;
361
+ const accessibilityClass = (typeof bootstrap !== 'undefined') ? 'visually-hidden' : 'sr-only';
362
+ const spinner = $(`
363
+ <div id="spinner" style="display: flex; align-items: center; justify-content: start; margin: 10px 0; padding: 10px;">
364
+ <div class="spinner-border text-primary" role="status" style="width: 1.5rem; height: 1.5rem; margin-right: 15px;">
365
+ <span class="${accessibilityClass}">Loading...</span>
366
+ </div>
367
+ <span style="font-weight: bold; font-size: 15px;">Loading...</span>
368
+ </div>
369
+ `);
370
+ $('#chat-container').append(spinner).scrollTop($('#chat-container')[0].scrollHeight);
371
+ };
372
+
373
+ /**
374
+ * Hides the loading spinner.
375
+ */
376
+ function hideSpinner() {
377
+ $('#spinner').fadeOut(function () {
378
+ $(this).remove();
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Converts a File object to a Base64 encoded string.
384
+ * @param {File} file The file to convert.
385
+ * @returns {Promise<{name: string, base64: string}>}
386
+ */
387
+ function toBase64(file) {
388
+ return new Promise((resolve, reject) => {
389
+ const reader = new FileReader();
390
+ reader.onload = () => resolve({name: file.name, base64: reader.result.split(",")[1]});
391
+ reader.onerror = reject;
392
+ reader.readAsDataURL(file);
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Displays the document validation results.
398
+ * @param {Array<object>} document_list
399
+ */
400
+ function display_document_validation(document_list) {
401
+ const requiredFields = ['document_name', 'document_type', 'causes', 'is_valid'];
402
+ for (const doc of document_list) {
403
+ if (!requiredFields.every(field => field in doc)) {
404
+ console.warn("Document with incorrect structure:", doc);
405
+ continue;
406
+ }
407
+ const docValidationSection = $('<div>').addClass('document-section card mt-2 mb-2');
408
+ const cardBody = $('<div>').addClass('card-body');
409
+ const headerDiv = $('<div>').addClass('d-flex justify-content-between align-items-center mb-2');
410
+ const filenameSpan = $(`
411
+ <div>
412
+ <span class="text-primary fw-bold">File: </span>
413
+ <span class="text-secondary">${doc.document_name}</span>
414
+ </div>`);
415
+ const badge_style = doc.is_valid ? 'bg-success' : 'bg-danger';
416
+ const documentBadge = $('<span>')
417
+ .addClass(`badge ${badge_style} p-2`)
418
+ .text(doc.document_type);
419
+ headerDiv.append(filenameSpan).append(documentBadge);
420
+ cardBody.append(headerDiv);
421
+
422
+ if (!doc.is_valid && doc.causes && doc.causes.length > 0) {
423
+ const rejectionSection = $('<div>').addClass('rejection-reasons mt-2');
424
+ rejectionSection.append('<h6 class="text-danger">Rejection Causes:</h6>');
425
+ const causesList = doc.causes.map(cause => `<li class="text-secondary">${cause}</li>`).join('');
426
+ rejectionSection.append(`<ul class="list-unstyled">${causesList}</ul>`);
427
+ cardBody.append(rejectionSection);
428
+ } else if (doc.is_valid) {
429
+ const validSection = $('<div>').addClass('mt-2');
430
+ validSection.append('<p class="text-success fw-bold">Valid document.</p>');
431
+ cardBody.append(validSection);
432
+ }
433
+ docValidationSection.append(cardBody);
434
+ displayBotMessage(docValidationSection);
435
+ }
436
+ }