iatoolkit 0.8.1__py3-none-any.whl → 0.63.4__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 (159) hide show
  1. iatoolkit/__init__.py +8 -34
  2. iatoolkit/base_company.py +14 -3
  3. iatoolkit/common/routes.py +83 -52
  4. iatoolkit/common/session_manager.py +0 -1
  5. iatoolkit/common/util.py +0 -27
  6. iatoolkit/iatoolkit.py +61 -46
  7. iatoolkit/infra/llm_client.py +7 -8
  8. iatoolkit/infra/openai_adapter.py +1 -1
  9. iatoolkit/infra/redis_session_manager.py +48 -2
  10. iatoolkit/repositories/database_manager.py +17 -2
  11. iatoolkit/repositories/models.py +31 -6
  12. iatoolkit/repositories/profile_repo.py +7 -2
  13. iatoolkit/services/auth_service.py +188 -0
  14. iatoolkit/services/branding_service.py +147 -0
  15. iatoolkit/services/dispatcher_service.py +10 -40
  16. iatoolkit/services/excel_service.py +15 -15
  17. iatoolkit/services/history_service.py +3 -12
  18. iatoolkit/services/jwt_service.py +15 -24
  19. iatoolkit/services/onboarding_service.py +43 -0
  20. iatoolkit/services/profile_service.py +97 -44
  21. iatoolkit/services/query_service.py +124 -81
  22. iatoolkit/services/tasks_service.py +1 -1
  23. iatoolkit/services/user_feedback_service.py +67 -31
  24. iatoolkit/services/user_session_context_service.py +112 -54
  25. iatoolkit/static/images/fernando.jpeg +0 -0
  26. iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
  27. iatoolkit/static/js/chat_history_button.js +126 -0
  28. iatoolkit/static/js/chat_logout_button.js +36 -0
  29. iatoolkit/static/js/chat_main.js +130 -220
  30. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  31. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  32. iatoolkit/static/js/chat_reload_button.js +52 -0
  33. iatoolkit/static/styles/chat_iatoolkit.css +329 -507
  34. iatoolkit/static/styles/chat_modal.css +95 -56
  35. iatoolkit/static/styles/landing_page.css +182 -0
  36. iatoolkit/static/styles/onboarding.css +169 -0
  37. iatoolkit/system_prompts/query_main.prompt +3 -12
  38. iatoolkit/templates/_company_header.html +20 -0
  39. iatoolkit/templates/_login_widget.html +40 -0
  40. iatoolkit/templates/base.html +8 -3
  41. iatoolkit/templates/change_password.html +54 -37
  42. iatoolkit/templates/chat.html +149 -66
  43. iatoolkit/templates/chat_modals.html +47 -18
  44. iatoolkit/templates/error.html +41 -8
  45. iatoolkit/templates/forgot_password.html +37 -24
  46. iatoolkit/templates/index.html +140 -0
  47. iatoolkit/templates/login_simulation.html +34 -0
  48. iatoolkit/templates/onboarding_shell.html +105 -0
  49. iatoolkit/templates/signup.html +64 -66
  50. iatoolkit/views/base_login_view.py +81 -0
  51. iatoolkit/views/change_password_view.py +23 -12
  52. iatoolkit/views/external_login_view.py +61 -28
  53. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  54. iatoolkit/views/forgot_password_view.py +23 -13
  55. iatoolkit/views/history_api_view.py +52 -0
  56. iatoolkit/views/home_view.py +58 -25
  57. iatoolkit/views/index_view.py +14 -0
  58. iatoolkit/views/init_context_api_view.py +68 -0
  59. iatoolkit/views/llmquery_api_view.py +45 -0
  60. iatoolkit/views/login_simulation_view.py +81 -0
  61. iatoolkit/views/login_view.py +118 -34
  62. iatoolkit/views/logout_api_view.py +45 -0
  63. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
  64. iatoolkit/views/signup_view.py +38 -29
  65. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  66. iatoolkit/views/tasks_review_api_view.py +55 -0
  67. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
  68. iatoolkit/views/verify_user_view.py +13 -8
  69. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
  70. iatoolkit-0.63.4.dist-info/RECORD +113 -0
  71. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
  72. iatoolkit/common/auth.py +0 -200
  73. iatoolkit/static/images/arrow_up.png +0 -0
  74. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. iatoolkit/static/images/logo_clinica.png +0 -0
  76. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. iatoolkit/static/images/logo_maxxa.png +0 -0
  78. iatoolkit/static/images/logo_notaria.png +0 -0
  79. iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. iatoolkit/static/images/logo_umayor.png +0 -0
  81. iatoolkit/static/images/upload.png +0 -0
  82. iatoolkit/static/js/chat_history.js +0 -117
  83. iatoolkit/templates/home.html +0 -201
  84. iatoolkit/templates/login.html +0 -43
  85. iatoolkit/views/chat_token_request_view.py +0 -98
  86. iatoolkit/views/chat_view.py +0 -51
  87. iatoolkit/views/download_file_view.py +0 -58
  88. iatoolkit/views/external_chat_login_view.py +0 -88
  89. iatoolkit/views/history_view.py +0 -57
  90. iatoolkit/views/llmquery_view.py +0 -65
  91. iatoolkit/views/tasks_review_view.py +0 -83
  92. iatoolkit-0.8.1.dist-info/RECORD +0 -175
  93. tests/__init__.py +0 -5
  94. tests/common/__init__.py +0 -0
  95. tests/common/test_auth.py +0 -279
  96. tests/common/test_routes.py +0 -42
  97. tests/common/test_session_manager.py +0 -59
  98. tests/common/test_util.py +0 -444
  99. tests/companies/__init__.py +0 -5
  100. tests/conftest.py +0 -36
  101. tests/infra/__init__.py +0 -5
  102. tests/infra/connectors/__init__.py +0 -5
  103. tests/infra/connectors/test_google_drive_connector.py +0 -107
  104. tests/infra/connectors/test_local_file_connector.py +0 -85
  105. tests/infra/connectors/test_s3_connector.py +0 -95
  106. tests/infra/test_call_service.py +0 -92
  107. tests/infra/test_database_manager.py +0 -59
  108. tests/infra/test_gemini_adapter.py +0 -137
  109. tests/infra/test_google_chat_app.py +0 -68
  110. tests/infra/test_llm_client.py +0 -165
  111. tests/infra/test_llm_proxy.py +0 -122
  112. tests/infra/test_mail_app.py +0 -94
  113. tests/infra/test_openai_adapter.py +0 -105
  114. tests/infra/test_redis_session_manager_service.py +0 -117
  115. tests/repositories/__init__.py +0 -5
  116. tests/repositories/test_database_manager.py +0 -87
  117. tests/repositories/test_document_repo.py +0 -76
  118. tests/repositories/test_llm_query_repo.py +0 -340
  119. tests/repositories/test_models.py +0 -38
  120. tests/repositories/test_profile_repo.py +0 -142
  121. tests/repositories/test_tasks_repo.py +0 -76
  122. tests/repositories/test_vs_repo.py +0 -107
  123. tests/services/__init__.py +0 -5
  124. tests/services/test_dispatcher_service.py +0 -274
  125. tests/services/test_document_service.py +0 -181
  126. tests/services/test_excel_service.py +0 -208
  127. tests/services/test_file_processor_service.py +0 -121
  128. tests/services/test_history_service.py +0 -164
  129. tests/services/test_jwt_service.py +0 -255
  130. tests/services/test_load_documents_service.py +0 -112
  131. tests/services/test_mail_service.py +0 -70
  132. tests/services/test_profile_service.py +0 -379
  133. tests/services/test_prompt_manager_service.py +0 -190
  134. tests/services/test_query_service.py +0 -243
  135. tests/services/test_search_service.py +0 -39
  136. tests/services/test_sql_service.py +0 -160
  137. tests/services/test_tasks_service.py +0 -252
  138. tests/services/test_user_feedback_service.py +0 -389
  139. tests/services/test_user_session_context_service.py +0 -132
  140. tests/views/__init__.py +0 -5
  141. tests/views/test_change_password_view.py +0 -191
  142. tests/views/test_chat_token_request_view.py +0 -188
  143. tests/views/test_chat_view.py +0 -98
  144. tests/views/test_download_file_view.py +0 -149
  145. tests/views/test_external_chat_login_view.py +0 -120
  146. tests/views/test_external_login_view.py +0 -102
  147. tests/views/test_file_store_view.py +0 -128
  148. tests/views/test_forgot_password_view.py +0 -142
  149. tests/views/test_history_view.py +0 -336
  150. tests/views/test_home_view.py +0 -61
  151. tests/views/test_llm_query_view.py +0 -154
  152. tests/views/test_login_view.py +0 -114
  153. tests/views/test_prompt_view.py +0 -111
  154. tests/views/test_signup_view.py +0 -140
  155. tests/views/test_tasks_review_view.py +0 -104
  156. tests/views/test_tasks_view.py +0 -130
  157. tests/views/test_user_feedback_view.py +0 -214
  158. tests/views/test_verify_user_view.py +0 -110
  159. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
@@ -1,38 +1,25 @@
1
1
  // Global variables for request management
2
- let currentAbortController = null;
3
2
  let isRequestInProgress = false;
4
-
3
+ let abortController = null;
5
4
  let selectedPrompt = null; // Will hold a lightweight prompt object
6
5
 
7
6
  $(document).ready(function () {
8
- // --- MAIN EVENT HANDLERS ---
7
+ // Si viene un Token retornado por login con APY-KEY se gatilla el redeem a una sesion de flask
8
+ if (window.redeemToken) {
9
+ const url = '/api/redeem_token';
10
+ // No await: dejamos que callToolkit maneje todo internamente
11
+ callToolkit(url, {'token': window.redeemToken}, "POST").catch(() => {});
12
+ }
13
+
14
+ // --- chat main event hadlers ---
9
15
  $('#send-button').on('click', handleChatMessage);
10
16
  $('#stop-button').on('click', abortCurrentRequest);
17
+ if (window.sendButtonColor)
18
+ $('#send-button i').css('color', window.sendButtonColor);
11
19
 
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
20
 
35
21
  // Handles Enter key press to send a message
22
+ const questionTextarea = $('#question');
36
23
  questionTextarea.on('keypress', function (event) {
37
24
  if (event.key === 'Enter' && !event.shiftKey) {
38
25
  event.preventDefault();
@@ -52,141 +39,101 @@ $(document).ready(function () {
52
39
 
53
40
  // Set the initial disabled state of the send button
54
41
  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
42
 
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
- }
43
+ });
119
44
 
120
45
 
121
46
  /**
122
47
  * Main function to handle sending a chat message.
123
48
  */
124
49
  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;
50
+ if (isRequestInProgress || $('#send-button').hasClass('disabled')) {
51
+ return;
52
+ }
129
53
 
130
- let displayMessage = question;
131
- let isEditable = true;
132
- const clientData = {};
54
+ isRequestInProgress = true;
55
+ toggleSendStopButtons(true);
133
56
 
134
- if (selectedPrompt) {
135
- displayMessage = selectedPrompt.description;
136
- isEditable = false; // Prompts are not editable
57
+ try {
58
+ const question = $('#question').val().trim();
59
+ const promptName = selectedPrompt ? selectedPrompt.prompt : null;
137
60
 
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
- });
61
+ let displayMessage = question;
62
+ let isEditable = true;
63
+ const clientData = {};
144
64
 
145
- // Append the collected parameter values to the display message
146
- const paramsString = Object.values(clientData).join(', ');
147
- if (paramsString) {
148
- displayMessage += `: ${paramsString}`;
65
+ if (selectedPrompt) {
66
+ displayMessage = selectedPrompt.description;
67
+ isEditable = false;
68
+
69
+ (selectedPrompt.custom_fields || []).forEach(field => {
70
+ const value = $('#' + field.data_key + '-id').val().trim();
71
+ if (value) {
72
+ clientData[field.data_key] = value;
73
+ }
74
+ });
75
+
76
+ const paramsString = Object.values(clientData).join(', ');
77
+ if (paramsString) { displayMessage += `: ${paramsString}`; }
149
78
  }
150
- }
151
79
 
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);
80
+ // Simplificado: Si no hay mensaje, el 'finally' se encargará de limpiar.
81
+ if (!displayMessage) {
82
+ return;
83
+ }
158
84
 
159
- resetAllInputs();
85
+ displayUserMessage(displayMessage, isEditable, question);
86
+ showSpinner();
87
+ resetAllInputs();
160
88
 
161
- const files = window.filePond.getFiles();
162
- const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
89
+ const files = window.filePond.getFiles();
90
+ const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
163
91
 
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
- };
92
+ const data = {
93
+ question: question,
94
+ prompt_name: promptName,
95
+ client_data: clientData,
96
+ files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
97
+ user_identifier: window.user_identifier
98
+ };
172
99
 
173
- try {
174
- const responseData = await callLLMAPI("/llm_query", data, "POST");
100
+ const responseData = await callToolkit("/api/llm_query", data, "POST");
175
101
  if (responseData && responseData.answer) {
176
102
  const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
177
103
  displayBotMessage(answerSection);
178
104
  }
179
105
  } catch (error) {
180
- console.error("Error in handleChatMessage:", error);
181
- // Implement error display logic as needed
106
+ if (error.name === 'AbortError') {
107
+ console.log('Petición abortada por el usuario.');
108
+
109
+ // Usando jQuery estándar para construir el elemento ---
110
+ const icon = $('<i>').addClass('bi bi-stop-circle me-2'); // Icono sin "fill" para un look más ligero
111
+ const textSpan = $('<span>').text('La generación de la respuesta ha sido detenida.');
112
+
113
+ const abortMessage = $('<div>')
114
+ .addClass('system-message')
115
+ .append(icon)
116
+ .append(textSpan);
117
+
118
+ displayBotMessage(abortMessage);
119
+ } else {
120
+ console.error("Error in handleChatMessage:", error);
121
+ const errorSection = $('<div>').addClass('error-section').append('<p>Ocurrió un error al procesar la solicitud.</p>');
122
+ displayBotMessage(errorSection);
123
+ }
182
124
  } finally {
125
+ // Este bloque se ejecuta siempre, garantizando que el estado se limpie.
126
+ isRequestInProgress = false;
183
127
  hideSpinner();
184
128
  toggleSendStopButtons(false);
185
129
  updateSendButtonState();
186
- window.filePond.removeFiles();
130
+ if (window.filePond) {
131
+ window.filePond.removeFiles();
132
+ }
187
133
  }
188
134
  };
189
135
 
136
+
190
137
  /**
191
138
  * Resets all inputs to their initial state.
192
139
  */
@@ -236,27 +183,6 @@ const toggleSendStopButtons = function (showStop) {
236
183
  $('#stop-button-container').toggle(showStop);
237
184
  };
238
185
 
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
186
 
261
187
  /**
262
188
  * Generic function to make API calls to the backend.
@@ -266,32 +192,47 @@ function resetSpecificDataInput() {
266
192
  * @param {number} timeoutMs - Timeout in milliseconds.
267
193
  * @returns {Promise<object|null>} The response data or null on error.
268
194
  */
269
- const callLLMAPI = async function(apiPath, data, method, timeoutMs = 500000) {
195
+ const callToolkit = async function(apiPath, data, method, timeoutMs = 500000) {
270
196
  const url = `${window.iatoolkit_base_url}/${window.companyShortName}${apiPath}`;
271
197
 
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);
198
+ abortController = new AbortController();
199
+ const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
280
200
 
281
201
  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
- });
202
+ const fetchOptions = {
203
+ method: method,
204
+ signal: abortController.signal,
205
+ credentials: 'include'
206
+ };
207
+
208
+ // Solo agrega body si el método lo soporta y hay datos
209
+ const methodUpper = (method || '').toUpperCase();
210
+ const canHaveBody = !['GET', 'HEAD'].includes(methodUpper);
211
+ if (canHaveBody && data !== undefined && data !== null) {
212
+ fetchOptions.body = JSON.stringify(data);
213
+ fetchOptions.headers = {"Content-Type": "application/json"};
214
+
215
+ }
216
+ const response = await fetch(url, fetchOptions);
217
+
289
218
  clearTimeout(timeoutId);
290
219
 
291
220
  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);
221
+ try {
222
+ // Intentamos leer el error como JSON, que es el formato esperado de nuestra API.
223
+ const errorData = await response.json();
224
+ const errorMessage = errorData.error_message || 'Error desconocido del servidor.';
225
+ const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
226
+ const endpointError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
227
+ displayBotMessage(endpointError);
228
+ } catch (e) {
229
+ // Si response.json() falla, es porque el cuerpo no era JSON (ej. un 502 con HTML).
230
+ // Mostramos un error genérico y más claro para el usuario.
231
+ const errorMessage = `Error de comunicación con el servidor (${response.status}). Por favor, intente de nuevo más tarde.`;
232
+ const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
233
+ const infrastructureError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
234
+ displayBotMessage(infrastructureError);
235
+ }
295
236
  return null;
296
237
  }
297
238
  return await response.json();
@@ -300,7 +241,17 @@ const callLLMAPI = async function(apiPath, data, method, timeoutMs = 500000) {
300
241
  if (error.name === 'AbortError') {
301
242
  throw error; // Re-throw to be handled by handleChatMessage
302
243
  } else {
303
- const commError = $('<div>').addClass('error-section').append(`<p>Connection error: ${error.message}</p>`);
244
+ // Log detallado en consola
245
+ console.error('Error de red en callToolkit:', {
246
+ url,
247
+ method,
248
+ error,
249
+ message: error?.message,
250
+ stack: error?.stack,
251
+ });
252
+ const friendlyMessage = "Ocurrió un error de red. Por favor, inténtalo de nuevo en unos momentos.";
253
+ const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
254
+ const commError = $('<div>').addClass('error-section').html(errorIcon + `<p>${friendlyMessage}</p>`);
304
255
  displayBotMessage(commError);
305
256
  }
306
257
  return null;
@@ -322,10 +273,11 @@ const displayUserMessage = function(message, isEditable, originalQuestion) {
322
273
  userMessage.append(messageText);
323
274
 
324
275
  if (isEditable) {
325
- const editIcon = $('<i>').addClass('bi bi-pencil-fill edit-icon').attr('title', 'Edit query').on('click', function () {
276
+ const editIcon = $('<i>').addClass('p-2 bi bi-pencil-fill edit-icon').attr('title', 'Edit query').on('click', function () {
326
277
  $('#question').val(originalQuestion).focus();
327
278
  autoResizeTextarea($('#question')[0]);
328
- updateSendButtonState();
279
+
280
+ $('#send-button').removeClass('disabled');
329
281
  });
330
282
  userMessage.append(editIcon);
331
283
  }
@@ -347,9 +299,8 @@ function displayBotMessage(section) {
347
299
  * Aborts the current in-progress API request.
348
300
  */
349
301
  const abortCurrentRequest = function () {
350
- if (currentAbortController && isRequestInProgress) {
351
- window.isManualAbort = true;
352
- currentAbortController.abort();
302
+ if (isRequestInProgress && abortController) {
303
+ abortController.abort();
353
304
  }
354
305
  };
355
306
 
@@ -361,10 +312,10 @@ const showSpinner = function () {
361
312
  const accessibilityClass = (typeof bootstrap !== 'undefined') ? 'visually-hidden' : 'sr-only';
362
313
  const spinner = $(`
363
314
  <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;">
315
+ <div class="spinner-border" role="status" style="width: 1.5rem; height: 1.5rem; margin-right: 15px;">
365
316
  <span class="${accessibilityClass}">Loading...</span>
366
317
  </div>
367
- <span style="font-weight: bold; font-size: 15px;">Loading...</span>
318
+ <span style="font-weight: bold; font-size: 15px;">Cargando...</span>
368
319
  </div>
369
320
  `);
370
321
  $('#chat-container').append(spinner).scrollTop($('#chat-container')[0].scrollHeight);
@@ -393,44 +344,3 @@ function toBase64(file) {
393
344
  });
394
345
  }
395
346
 
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
- }
@@ -0,0 +1,97 @@
1
+ (function (global) {
2
+ function qs(root, sel) { return (typeof sel === 'string') ? root.querySelector(sel) : sel; }
3
+
4
+ function createDots(container, count, activeIdx, activeColor) {
5
+ container.innerHTML = '';
6
+ for (let i = 0; i < count; i++) {
7
+ const d = document.createElement('div');
8
+ if (i === activeIdx) d.classList.add('active');
9
+ d.style.width = '10px';
10
+ d.style.height = '10px';
11
+ d.style.borderRadius = '50%';
12
+ d.style.backgroundColor = i === activeIdx ? (activeColor || 'var(--brand-primary-color, #FF5100)') : '#ddd';
13
+ d.style.transition = 'background-color .3s';
14
+ container.appendChild(d);
15
+ }
16
+ }
17
+
18
+ function initOnboarding(opts) {
19
+ const {
20
+ mode = 'modal',
21
+ cards = [],
22
+ ui = {},
23
+ autoRotateMs = 5000,
24
+ shell = {}
25
+ } = opts;
26
+
27
+ const root = document;
28
+ const elIcon = qs(root, ui.icon);
29
+ const elTitle = qs(root, ui.title);
30
+ const elText = qs(root, ui.text);
31
+ const elDots = qs(root, ui.dots);
32
+ const elPrev = qs(root, ui.prev);
33
+ const elNext = qs(root, ui.next);
34
+
35
+ let idx = 0;
36
+ let autoTimer = null;
37
+
38
+ function hasCards() { return Array.isArray(cards) && cards.length > 0; }
39
+
40
+ function render() {
41
+ if (!hasCards()) return;
42
+ const c = cards[idx] || {};
43
+ if (elIcon) elIcon.innerHTML = `<i class="${c.icon || 'bi bi-lightbulb'}"></i>`;
44
+ if (elTitle) elTitle.textContent = c.title || '';
45
+ if (elText) elText.innerHTML = c.text || '';
46
+ if (elDots) createDots(elDots, cards.length, idx);
47
+ }
48
+
49
+ function next() { if (!hasCards()) return; idx = (idx + 1) % cards.length; render(); }
50
+ function prev() { if (!hasCards()) return; idx = (idx - 1 + cards.length) % cards.length; render(); }
51
+
52
+ function startAuto() {
53
+ stopAuto();
54
+ if (!hasCards()) return;
55
+ autoTimer = setInterval(next, autoRotateMs);
56
+ }
57
+ function stopAuto() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } }
58
+
59
+ function setupShellIfNeeded() {
60
+ if (mode !== 'shell') return;
61
+ const loader = ui.loader ? qs(root, ui.loader) : null;
62
+ const container = ui.container ? qs(root, ui.container) : null;
63
+ if (!container || !shell.iframeSrc) return;
64
+
65
+ const iframe = document.createElement('iframe');
66
+ iframe.src = shell.iframeSrc;
67
+ iframe.style.width = '100%';
68
+ iframe.style.height = '100%';
69
+ iframe.style.border = 'none';
70
+ iframe.style.display = 'none';
71
+
72
+ iframe.onload = function () {
73
+ iframe.style.display = 'block';
74
+ if (loader) {
75
+ loader.style.opacity = '0';
76
+ setTimeout(() => loader.style.display = 'none', 500);
77
+ }
78
+ };
79
+ container.appendChild(iframe);
80
+ }
81
+
82
+ if (elPrev) elPrev.addEventListener('click', () => { prev(); startAuto(); });
83
+ if (elNext) elNext.addEventListener('click', () => { next(); startAuto(); });
84
+
85
+ function start() {
86
+ idx = 0;
87
+ render();
88
+ startAuto();
89
+ if (mode === 'shell') setupShellIfNeeded();
90
+ }
91
+
92
+ return { start, stop: stopAuto, next, prev, hasCards };
93
+ }
94
+
95
+ // Export global
96
+ global.initOnboarding = initOnboarding;
97
+ })(window);
@@ -0,0 +1,94 @@
1
+ $(document).ready(function () {
2
+ // --- PROMPT ASSISTANT FUNCTIONALITY ---
3
+ const $promptCollapse = $('#prompt-assistant-collapse');
4
+
5
+ if ($promptCollapse.length) {
6
+ $promptCollapse.on('shown.bs.collapse', function () {
7
+ // Scroll to bottom smoothly when the collapse is shown
8
+ $('html, body').animate(
9
+ { scrollTop: $(document).height() },
10
+ 'slow'
11
+ );
12
+ });
13
+ }
14
+
15
+ $('.input-area').on('click', '.dropdown-menu a.dropdown-item', function (event) {
16
+ event.preventDefault();
17
+ const promptData = $(this).data();
18
+
19
+ const promptObject = {
20
+ prompt: promptData.promptName,
21
+ description: promptData.promptDescription,
22
+ custom_fields: typeof promptData.customFields === 'string' ? JSON.parse(promptData.customFields) : promptData.customFields
23
+ };
24
+ selectPrompt(promptObject);
25
+ });
26
+
27
+ // Handles the 'clear' button for the prompt selector
28
+ $('#clear-selection-button').on('click', function() {
29
+ resetPromptSelection();
30
+ updateSendButtonState();
31
+ });
32
+ });
33
+
34
+ /**
35
+ * Handles the selection of a prompt from the dropdown.
36
+ * @param {object} prompt The prompt object read from data attributes.
37
+ */
38
+ function selectPrompt(prompt) {
39
+ selectedPrompt = prompt;
40
+
41
+ // Update the dropdown button to show the selected prompt's description
42
+ $('#prompt-select-button').text(prompt.description).addClass('item-selected');
43
+ $('#clear-selection-button').show();
44
+
45
+ // Clear the main textarea, as we are now in "prompt mode"
46
+ $('#question').val('');
47
+ autoResizeTextarea($('#question')[0]); // Reset height after clearing
48
+
49
+ // Store values in hidden fields for backward compatibility or other uses
50
+ $('#prompt-select-value').val(prompt.prompt);
51
+ $('#prompt-select-description').val(prompt.description);
52
+
53
+ // Render the dynamic input fields required by the selected prompt
54
+ renderDynamicInputs(prompt.custom_fields || []);
55
+ updateSendButtonState();
56
+ }
57
+
58
+ /**
59
+ * Resets the prompt selection and clears associated UI elements.
60
+ */
61
+ function resetPromptSelection() {
62
+ selectedPrompt = null;
63
+
64
+ $('#prompt-select-button').text('Prompts disponibles ....').removeClass('item-selected');
65
+ $('#clear-selection-button').hide();
66
+ $('#prompt-select-value').val('');
67
+ $('#prompt-select-description').val('');
68
+
69
+ // Clear any dynamically generated input fields
70
+ $('#dynamic-inputs-container').empty();
71
+ }
72
+
73
+ /**
74
+ * Renders the custom input fields for the selected prompt.
75
+ * @param {Array<object>} fields The array of custom field configurations.
76
+ */
77
+ function renderDynamicInputs(fields) {
78
+ const container = $('#dynamic-inputs-container');
79
+ container.empty();
80
+
81
+ const row = $('<div class="row g-2"></div>');
82
+ fields.forEach(field => {
83
+ const colDiv = $('<div class="col-md"></div>');
84
+ const formFloating = $('<div class="form-floating"></div>');
85
+ const input = $(`<input type="${field.type || 'text'}" class="form-control form-control-soft" id="${field.data_key}-id" ">`);
86
+ const label = $(`<label for="${field.data_key}-id">${field.label}</label>`);
87
+
88
+ formFloating.append(input, label);
89
+ colDiv.append(formFloating);
90
+ row.append(colDiv);
91
+ });
92
+
93
+ container.append(row);
94
+ }