iatoolkit 0.4.2__py3-none-any.whl → 0.66.2__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.
- iatoolkit/__init__.py +13 -35
- iatoolkit/base_company.py +74 -8
- iatoolkit/cli_commands.py +15 -23
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +46 -0
- iatoolkit/common/routes.py +141 -0
- iatoolkit/common/session_manager.py +24 -0
- iatoolkit/common/util.py +348 -0
- iatoolkit/company_registry.py +7 -8
- iatoolkit/iatoolkit.py +169 -96
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/call_service.py +140 -0
- iatoolkit/infra/connectors/__init__.py +5 -0
- iatoolkit/infra/connectors/file_connector.py +17 -0
- iatoolkit/infra/connectors/file_connector_factory.py +57 -0
- iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
- iatoolkit/infra/connectors/google_drive_connector.py +68 -0
- iatoolkit/infra/connectors/local_file_connector.py +46 -0
- iatoolkit/infra/connectors/s3_connector.py +33 -0
- iatoolkit/infra/gemini_adapter.py +356 -0
- iatoolkit/infra/google_chat_app.py +57 -0
- iatoolkit/infra/llm_client.py +429 -0
- iatoolkit/infra/llm_proxy.py +139 -0
- iatoolkit/infra/llm_response.py +40 -0
- iatoolkit/infra/mail_app.py +145 -0
- iatoolkit/infra/openai_adapter.py +90 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +144 -0
- iatoolkit/locales/es.yaml +140 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +110 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/llm_query_repo.py +91 -0
- iatoolkit/repositories/models.py +336 -0
- iatoolkit/repositories/profile_repo.py +123 -0
- iatoolkit/repositories/tasks_repo.py +52 -0
- iatoolkit/repositories/vs_repo.py +139 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +6 -6
- iatoolkit/services/branding_service.py +149 -0
- {services → iatoolkit/services}/dispatcher_service.py +39 -99
- {services → iatoolkit/services}/document_service.py +5 -5
- {services → iatoolkit/services}/excel_service.py +27 -21
- {services → iatoolkit/services}/file_processor_service.py +5 -5
- iatoolkit/services/help_content_service.py +30 -0
- {services → iatoolkit/services}/history_service.py +8 -16
- iatoolkit/services/i18n_service.py +104 -0
- {services → iatoolkit/services}/jwt_service.py +18 -27
- iatoolkit/services/language_service.py +77 -0
- {services → iatoolkit/services}/load_documents_service.py +19 -14
- {services → iatoolkit/services}/mail_service.py +5 -5
- iatoolkit/services/onboarding_service.py +43 -0
- {services → iatoolkit/services}/profile_service.py +155 -89
- {services → iatoolkit/services}/prompt_manager_service.py +26 -11
- {services → iatoolkit/services}/query_service.py +142 -104
- {services → iatoolkit/services}/search_service.py +21 -5
- {services → iatoolkit/services}/sql_service.py +24 -6
- {services → iatoolkit/services}/tasks_service.py +10 -10
- iatoolkit/services/user_feedback_service.py +103 -0
- iatoolkit/services/user_session_context_service.py +143 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_filepond.js +85 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +112 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +364 -0
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +592 -0
- iatoolkit/static/styles/chat_modal.css +169 -0
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/llm_output.css +115 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +5 -15
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/about.html +13 -0
- iatoolkit/templates/base.html +65 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +287 -0
- iatoolkit/templates/chat_modals.html +181 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +50 -0
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +104 -0
- iatoolkit/templates/signup.html +76 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +92 -0
- iatoolkit/views/change_password_view.py +117 -0
- iatoolkit/views/external_login_view.py +73 -0
- iatoolkit/views/file_store_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +72 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +61 -0
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +73 -0
- iatoolkit/views/llmquery_api_view.py +57 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +153 -0
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/signup_view.py +94 -0
- iatoolkit/views/tasks_api_view.py +72 -0
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +62 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
- iatoolkit-0.66.2.dist-info/RECORD +119 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.4.2.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// Global variables for request management
|
|
2
|
+
let isRequestInProgress = false;
|
|
3
|
+
let abortController = null;
|
|
4
|
+
let selectedPrompt = null; // Will hold a lightweight prompt object
|
|
5
|
+
|
|
6
|
+
$(document).ready(function () {
|
|
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
|
+
const layoutContainer = document.querySelector('.chat-layout-container');
|
|
15
|
+
const promptAssistantCollapse = document.getElementById('prompt-assistant-collapse');
|
|
16
|
+
|
|
17
|
+
if (layoutContainer && promptAssistantCollapse) {
|
|
18
|
+
promptAssistantCollapse.addEventListener('show.bs.collapse', function () {
|
|
19
|
+
layoutContainer.classList.add('prompt-assistant-open');
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
22
|
+
}, 300);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
promptAssistantCollapse.addEventListener('hide.bs.collapse', function () {
|
|
26
|
+
layoutContainer.classList.remove('prompt-assistant-open');
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- chat main event hadlers ---
|
|
31
|
+
$('#send-button').on('click', handleChatMessage);
|
|
32
|
+
$('#stop-button').on('click', abortCurrentRequest);
|
|
33
|
+
if (window.sendButtonColor)
|
|
34
|
+
$('#send-button i').css('color', window.sendButtonColor);
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Handles Enter key press to send a message
|
|
38
|
+
const questionTextarea = $('#question');
|
|
39
|
+
questionTextarea.on('keypress', function (event) {
|
|
40
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
handleChatMessage();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Handles auto-resizing and enables the send button on input
|
|
47
|
+
questionTextarea.on('input', function () {
|
|
48
|
+
autoResizeTextarea(this);
|
|
49
|
+
// If the user types, it overrides any prompt selection
|
|
50
|
+
if (selectedPrompt) {
|
|
51
|
+
resetPromptSelection();
|
|
52
|
+
}
|
|
53
|
+
updateSendButtonState();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Set the initial disabled state of the send button
|
|
57
|
+
updateSendButtonState();
|
|
58
|
+
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Main function to handle sending a chat message.
|
|
64
|
+
*/
|
|
65
|
+
const handleChatMessage = async function () {
|
|
66
|
+
if (isRequestInProgress || $('#send-button').hasClass('disabled')) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isRequestInProgress = true;
|
|
71
|
+
toggleSendStopButtons(true);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const question = $('#question').val().trim();
|
|
75
|
+
const promptName = selectedPrompt ? selectedPrompt.prompt : null;
|
|
76
|
+
|
|
77
|
+
let displayMessage = question;
|
|
78
|
+
let isEditable = true;
|
|
79
|
+
const clientData = {};
|
|
80
|
+
|
|
81
|
+
if (selectedPrompt) {
|
|
82
|
+
displayMessage = selectedPrompt.description;
|
|
83
|
+
isEditable = false;
|
|
84
|
+
|
|
85
|
+
(selectedPrompt.custom_fields || []).forEach(field => {
|
|
86
|
+
const value = $('#' + field.data_key + '-id').val().trim();
|
|
87
|
+
if (value) {
|
|
88
|
+
clientData[field.data_key] = value;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const paramsString = Object.values(clientData).join(', ');
|
|
93
|
+
if (paramsString) { displayMessage += `: ${paramsString}`; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Simplificado: Si no hay mensaje, el 'finally' se encargará de limpiar.
|
|
97
|
+
if (!displayMessage) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
displayUserMessage(displayMessage, isEditable, question);
|
|
102
|
+
showSpinner();
|
|
103
|
+
resetAllInputs();
|
|
104
|
+
|
|
105
|
+
const files = window.filePond.getFiles();
|
|
106
|
+
const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
|
|
107
|
+
|
|
108
|
+
const data = {
|
|
109
|
+
question: question,
|
|
110
|
+
prompt_name: promptName,
|
|
111
|
+
client_data: clientData,
|
|
112
|
+
files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
|
|
113
|
+
user_identifier: window.user_identifier
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const responseData = await callToolkit("/api/llm_query", data, "POST");
|
|
117
|
+
if (responseData && responseData.answer) {
|
|
118
|
+
const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
|
|
119
|
+
displayBotMessage(answerSection);
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.name === 'AbortError') {
|
|
123
|
+
console.log('Petición abortada por el usuario.');
|
|
124
|
+
|
|
125
|
+
// Usando jQuery estándar para construir el elemento ---
|
|
126
|
+
const icon = $('<i>').addClass('bi bi-stop-circle me-2'); // Icono sin "fill" para un look más ligero
|
|
127
|
+
const textSpan = $('<span>').text('La generación de la respuesta ha sido detenida.');
|
|
128
|
+
|
|
129
|
+
const abortMessage = $('<div>')
|
|
130
|
+
.addClass('system-message')
|
|
131
|
+
.append(icon)
|
|
132
|
+
.append(textSpan);
|
|
133
|
+
|
|
134
|
+
displayBotMessage(abortMessage);
|
|
135
|
+
} else {
|
|
136
|
+
console.error("Error in handleChatMessage:", error);
|
|
137
|
+
const errorSection = $('<div>').addClass('error-section').append('<p>Ocurrió un error al procesar la solicitud.</p>');
|
|
138
|
+
displayBotMessage(errorSection);
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
// Este bloque se ejecuta siempre, garantizando que el estado se limpie.
|
|
142
|
+
isRequestInProgress = false;
|
|
143
|
+
hideSpinner();
|
|
144
|
+
toggleSendStopButtons(false);
|
|
145
|
+
updateSendButtonState();
|
|
146
|
+
if (window.filePond) {
|
|
147
|
+
window.filePond.removeFiles();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Resets all inputs to their initial state.
|
|
155
|
+
*/
|
|
156
|
+
function resetAllInputs() {
|
|
157
|
+
resetPromptSelection();
|
|
158
|
+
$('#question').val('');
|
|
159
|
+
autoResizeTextarea($('#question')[0]);
|
|
160
|
+
|
|
161
|
+
const promptCollapseEl = document.getElementById('prompt-assistant-collapse');
|
|
162
|
+
const promptCollapse = bootstrap.Collapse.getInstance(promptCollapseEl);
|
|
163
|
+
if (promptCollapse) {
|
|
164
|
+
promptCollapse.hide();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
updateSendButtonState();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Enables or disables the send button based on whether there's content
|
|
172
|
+
* in the textarea or a prompt has been selected.
|
|
173
|
+
*/
|
|
174
|
+
function updateSendButtonState() {
|
|
175
|
+
const question = $('#question').val().trim();
|
|
176
|
+
const isPromptSelected = selectedPrompt !== null;
|
|
177
|
+
|
|
178
|
+
if (isPromptSelected || question) {
|
|
179
|
+
$('#send-button').removeClass('disabled');
|
|
180
|
+
} else {
|
|
181
|
+
$('#send-button').addClass('disabled');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Auto-resizes the textarea to fit its content.
|
|
187
|
+
*/
|
|
188
|
+
function autoResizeTextarea(element) {
|
|
189
|
+
element.style.height = 'auto';
|
|
190
|
+
element.style.height = (element.scrollHeight) + 'px';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Toggles the main action button between 'Send' and 'Stop'.
|
|
195
|
+
* @param {boolean} showStop - If true, shows the Stop button. Otherwise, shows the Send button.
|
|
196
|
+
*/
|
|
197
|
+
const toggleSendStopButtons = function (showStop) {
|
|
198
|
+
$('#send-button-container').toggle(!showStop);
|
|
199
|
+
$('#stop-button-container').toggle(showStop);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Generic function to make API calls to the backend.
|
|
204
|
+
* @param {string} apiPath - The API endpoint path.
|
|
205
|
+
* @param {object} data - The data payload to send.
|
|
206
|
+
* @param {string} method - The HTTP method (e.g., 'POST').
|
|
207
|
+
* @param {number} timeoutMs - Timeout in milliseconds.
|
|
208
|
+
* @returns {Promise<object|null>} The response data or null on error.
|
|
209
|
+
*/
|
|
210
|
+
const callToolkit = async function(apiPath, data, method, timeoutMs = 500000) {
|
|
211
|
+
const url = `${window.iatoolkit_base_url}/${window.companyShortName}${apiPath}`;
|
|
212
|
+
|
|
213
|
+
abortController = new AbortController();
|
|
214
|
+
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const fetchOptions = {
|
|
218
|
+
method: method,
|
|
219
|
+
signal: abortController.signal,
|
|
220
|
+
credentials: 'include'
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Solo agrega body si el método lo soporta y hay datos
|
|
224
|
+
const methodUpper = (method || '').toUpperCase();
|
|
225
|
+
const canHaveBody = !['GET', 'HEAD'].includes(methodUpper);
|
|
226
|
+
if (canHaveBody && data !== undefined && data !== null) {
|
|
227
|
+
fetchOptions.body = JSON.stringify(data);
|
|
228
|
+
fetchOptions.headers = {"Content-Type": "application/json"};
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
const response = await fetch(url, fetchOptions);
|
|
232
|
+
|
|
233
|
+
clearTimeout(timeoutId);
|
|
234
|
+
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
try {
|
|
237
|
+
// Intentamos leer el error como JSON, que es el formato esperado de nuestra API.
|
|
238
|
+
const errorData = await response.json();
|
|
239
|
+
const errorMessage = errorData.error_message || t_js('unknown_server_error'); // <-- Translation
|
|
240
|
+
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
241
|
+
const endpointError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
|
|
242
|
+
displayBotMessage(endpointError);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
// Si response.json() falla, es porque el cuerpo no era JSON (ej. un 502 con HTML).
|
|
245
|
+
// Mostramos un error genérico y más claro para el usuario.
|
|
246
|
+
const errorMessage = `Error de comunicación con el servidor (${response.status}). Por favor, intente de nuevo más tarde.`;
|
|
247
|
+
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
248
|
+
const infrastructureError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
|
|
249
|
+
displayBotMessage(infrastructureError);
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
return await response.json();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
clearTimeout(timeoutId);
|
|
256
|
+
if (error.name === 'AbortError') {
|
|
257
|
+
throw error; // Re-throw to be handled by handleChatMessage
|
|
258
|
+
} else {
|
|
259
|
+
// Log detallado en consola
|
|
260
|
+
console.error('Network error in callToolkit:', {
|
|
261
|
+
url,
|
|
262
|
+
method,
|
|
263
|
+
error,
|
|
264
|
+
message: error?.message,
|
|
265
|
+
stack: error?.stack,
|
|
266
|
+
});
|
|
267
|
+
const friendlyMessage = t_js('network_error');
|
|
268
|
+
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
269
|
+
const commError = $('<div>').addClass('error-section').html(errorIcon + `<p>${friendlyMessage}</p>`);
|
|
270
|
+
displayBotMessage(commError);
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Displays the user's message in the chat container.
|
|
279
|
+
* @param {string} message - The full message string to display.
|
|
280
|
+
* @param {boolean} isEditable - Determines if the edit icon should be shown.
|
|
281
|
+
* @param {string} originalQuestion - The original text to put back in the textarea for editing.
|
|
282
|
+
*/
|
|
283
|
+
const displayUserMessage = function(message, isEditable, originalQuestion) {
|
|
284
|
+
const chatContainer = $('#chat-container');
|
|
285
|
+
const userMessage = $('<div>').addClass('message shadow-sm');
|
|
286
|
+
const messageText = $('<span>').text(message);
|
|
287
|
+
|
|
288
|
+
userMessage.append(messageText);
|
|
289
|
+
|
|
290
|
+
if (isEditable) {
|
|
291
|
+
const editIcon = $('<i>').addClass('p-2 bi bi-pencil-fill edit-icon edit-pencil').attr('title', 'Edit query').on('click', function () {
|
|
292
|
+
$('#question').val(originalQuestion)
|
|
293
|
+
autoResizeTextarea($('#question')[0]);
|
|
294
|
+
$('#send-button').removeClass('disabled');
|
|
295
|
+
|
|
296
|
+
if (window.innerWidth > 768)
|
|
297
|
+
$('#question').focus();
|
|
298
|
+
});
|
|
299
|
+
userMessage.append(editIcon);
|
|
300
|
+
}
|
|
301
|
+
chatContainer.append(userMessage);
|
|
302
|
+
chatContainer.scrollTop(chatContainer[0].scrollHeight);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Appends a message from the bot to the chat container.
|
|
307
|
+
* @param {jQuery} section - The jQuery object to append.
|
|
308
|
+
*/
|
|
309
|
+
function displayBotMessage(section) {
|
|
310
|
+
const chatContainer = $('#chat-container');
|
|
311
|
+
chatContainer.append(section);
|
|
312
|
+
chatContainer.scrollTop(chatContainer[0].scrollHeight);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Aborts the current in-progress API request.
|
|
317
|
+
*/
|
|
318
|
+
const abortCurrentRequest = function () {
|
|
319
|
+
if (isRequestInProgress && abortController) {
|
|
320
|
+
abortController.abort();
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Shows the loading spinner in the chat.
|
|
326
|
+
*/
|
|
327
|
+
const showSpinner = function () {
|
|
328
|
+
if ($('#spinner').length) return;
|
|
329
|
+
const accessibilityClass = (typeof bootstrap !== 'undefined') ? 'visually-hidden' : 'sr-only';
|
|
330
|
+
const spinnerText = t_js('loading');
|
|
331
|
+
const spinner = $(`
|
|
332
|
+
<div id="spinner" style="display: flex; align-items: center; justify-content: start; margin: 10px 0; padding: 10px;">
|
|
333
|
+
<div class="spinner-border" role="status" style="width: 1.5rem; height: 1.5rem; margin-right: 15px;">
|
|
334
|
+
<span class="${accessibilityClass}">Loading...</span>
|
|
335
|
+
</div>
|
|
336
|
+
<span style="font-weight: bold; font-size: 15px;">${spinnerText}</span>
|
|
337
|
+
</div>
|
|
338
|
+
`);
|
|
339
|
+
$('#chat-container').append(spinner).scrollTop($('#chat-container')[0].scrollHeight);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Hides the loading spinner.
|
|
344
|
+
*/
|
|
345
|
+
function hideSpinner() {
|
|
346
|
+
$('#spinner').fadeOut(function () {
|
|
347
|
+
$(this).remove();
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Converts a File object to a Base64 encoded string.
|
|
353
|
+
* @param {File} file The file to convert.
|
|
354
|
+
* @returns {Promise<{name: string, base64: string}>}
|
|
355
|
+
*/
|
|
356
|
+
function toBase64(file) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const reader = new FileReader();
|
|
359
|
+
reader.onload = () => resolve({name: file.name, base64: reader.result.split(",")[1]});
|
|
360
|
+
reader.onerror = reject;
|
|
361
|
+
reader.readAsDataURL(file);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
$(document).ready(function () {
|
|
2
|
+
$('#force-reload-button').on('click', function() {
|
|
3
|
+
reloadButton(this);
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
async function reloadButton(button) {
|
|
7
|
+
const originalIconClass = 'bi bi-arrow-clockwise';
|
|
8
|
+
const spinnerIconClass = 'spinner-border spinner-border-sm';
|
|
9
|
+
|
|
10
|
+
// Configuración de Toastr para que aparezca abajo a la derecha
|
|
11
|
+
toastr.options = {"positionClass": "toast-bottom-right", "preventDuplicates": true};
|
|
12
|
+
|
|
13
|
+
// 1. Deshabilitar y mostrar spinner
|
|
14
|
+
button.disabled = true;
|
|
15
|
+
const icon = button.querySelector('i');
|
|
16
|
+
icon.className = spinnerIconClass;
|
|
17
|
+
toastr.info(t_js('reload_init'));
|
|
18
|
+
|
|
19
|
+
// 2. prepare the api parameters
|
|
20
|
+
const apiPath = '/api/init-context';
|
|
21
|
+
const payload = {'user_identifier': window.user_identifier};
|
|
22
|
+
|
|
23
|
+
// 3. make the call to callToolkit
|
|
24
|
+
const data = await callToolkit(apiPath, payload, 'POST');
|
|
25
|
+
if (data) {
|
|
26
|
+
if (data.status === 'OK')
|
|
27
|
+
toastr.success(data.message || 'Contexto reloaded.');
|
|
28
|
+
else
|
|
29
|
+
toastr.error(data.error_message || 'error during reload');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
button.disabled = false;
|
|
33
|
+
icon.className = originalIconClass;
|
|
34
|
+
}
|
|
35
|
+
});
|