iatoolkit 0.63.1__py3-none-any.whl → 0.69.0__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.
- iatoolkit/__init__.py +0 -2
- iatoolkit/base_company.py +1 -26
- iatoolkit/common/routes.py +11 -2
- iatoolkit/common/session_manager.py +2 -0
- iatoolkit/common/util.py +17 -0
- iatoolkit/company_registry.py +1 -2
- iatoolkit/iatoolkit.py +39 -6
- iatoolkit/locales/en.yaml +167 -0
- iatoolkit/locales/es.yaml +163 -0
- iatoolkit/repositories/database_manager.py +8 -3
- iatoolkit/repositories/document_repo.py +1 -1
- iatoolkit/repositories/models.py +1 -4
- iatoolkit/repositories/profile_repo.py +0 -4
- iatoolkit/services/auth_service.py +14 -9
- iatoolkit/services/branding_service.py +36 -24
- iatoolkit/services/company_context_service.py +145 -0
- iatoolkit/services/configuration_service.py +133 -0
- iatoolkit/services/dispatcher_service.py +51 -48
- iatoolkit/services/document_service.py +5 -2
- iatoolkit/services/excel_service.py +15 -11
- iatoolkit/services/file_processor_service.py +4 -12
- iatoolkit/services/history_service.py +8 -7
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +7 -9
- iatoolkit/services/language_service.py +83 -0
- iatoolkit/services/load_documents_service.py +4 -4
- iatoolkit/services/mail_service.py +9 -4
- iatoolkit/services/profile_service.py +61 -38
- iatoolkit/services/prompt_manager_service.py +20 -16
- iatoolkit/services/query_service.py +19 -15
- iatoolkit/services/search_service.py +11 -4
- iatoolkit/services/sql_service.py +55 -25
- iatoolkit/services/user_feedback_service.py +16 -14
- iatoolkit/static/js/chat_feedback_button.js +57 -87
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +48 -65
- iatoolkit/static/js/chat_main.js +27 -24
- iatoolkit/static/js/chat_onboarding_button.js +6 -0
- iatoolkit/static/js/chat_reload_button.js +28 -45
- iatoolkit/static/styles/chat_iatoolkit.css +223 -315
- iatoolkit/static/styles/chat_modal.css +63 -97
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +0 -1
- iatoolkit/static/styles/onboarding.css +7 -0
- iatoolkit/templates/_company_header.html +6 -2
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +34 -19
- iatoolkit/templates/change_password.html +22 -20
- iatoolkit/templates/chat.html +59 -27
- iatoolkit/templates/chat_modals.html +114 -74
- iatoolkit/templates/error.html +12 -13
- iatoolkit/templates/forgot_password.html +11 -7
- iatoolkit/templates/index.html +8 -3
- iatoolkit/templates/login_simulation.html +17 -6
- iatoolkit/templates/onboarding_shell.html +4 -2
- iatoolkit/templates/signup.html +14 -14
- iatoolkit/views/base_login_view.py +19 -9
- iatoolkit/views/change_password_view.py +50 -35
- iatoolkit/views/external_login_view.py +1 -1
- iatoolkit/views/forgot_password_view.py +21 -22
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +13 -9
- iatoolkit/views/home_view.py +30 -39
- iatoolkit/views/init_context_api_view.py +16 -11
- iatoolkit/views/llmquery_api_view.py +38 -26
- iatoolkit/views/login_simulation_view.py +14 -2
- iatoolkit/views/login_view.py +52 -40
- iatoolkit/views/logout_api_view.py +26 -22
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +6 -6
- iatoolkit/views/signup_view.py +27 -27
- iatoolkit/views/user_feedback_api_view.py +19 -18
- iatoolkit/views/verify_user_view.py +29 -30
- {iatoolkit-0.63.1.dist-info → iatoolkit-0.69.0.dist-info}/METADATA +40 -22
- iatoolkit-0.69.0.dist-info/RECORD +120 -0
- iatoolkit-0.69.0.dist-info/licenses/LICENSE +21 -0
- iatoolkit/services/onboarding_service.py +0 -43
- iatoolkit/static/styles/chat_info.css +0 -53
- iatoolkit/templates/header.html +0 -31
- iatoolkit/templates/test.html +0 -9
- iatoolkit-0.63.1.dist-info/RECORD +0 -112
- {iatoolkit-0.63.1.dist-info → iatoolkit-0.69.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.63.1.dist-info → iatoolkit-0.69.0.dist-info}/top_level.txt +0 -0
|
@@ -4,57 +4,87 @@
|
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
7
|
-
|
|
8
7
|
from iatoolkit.common.util import Utility
|
|
8
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
9
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
10
|
from sqlalchemy import text
|
|
10
|
-
from injector import inject
|
|
11
|
+
from injector import inject, singleton
|
|
11
12
|
import json
|
|
12
|
-
|
|
13
|
+
import logging
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
@singleton
|
|
15
17
|
class SqlService:
|
|
18
|
+
"""
|
|
19
|
+
Manages database connections and executes SQL statements.
|
|
20
|
+
It maintains a cache of named DatabaseManager instances to avoid reconnecting.
|
|
21
|
+
"""
|
|
22
|
+
|
|
16
23
|
@inject
|
|
17
|
-
def __init__(self,
|
|
24
|
+
def __init__(self,
|
|
25
|
+
util: Utility,
|
|
26
|
+
i18n_service: I18nService):
|
|
18
27
|
self.util = util
|
|
28
|
+
self.i18n_service = i18n_service
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
# Cache for database connections
|
|
31
|
+
self._db_connections: dict[str, DatabaseManager] = {}
|
|
32
|
+
|
|
33
|
+
def register_database(self, db_name: str, db_uri: str):
|
|
34
|
+
"""
|
|
35
|
+
Creates and caches a DatabaseManager instance for a given database name and URI.
|
|
36
|
+
If a database with the same name is already registered, it does nothing.
|
|
21
37
|
"""
|
|
22
|
-
|
|
38
|
+
if db_name in self._db_connections:
|
|
39
|
+
return
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
This list is then serialized to a JSON string.
|
|
28
|
-
If an exception occurs during execution, the transaction is rolled back,
|
|
29
|
-
and a custom IAToolkitException is raised.
|
|
41
|
+
logging.debug(f"Registering and creating connection for database: '{db_name}'")
|
|
42
|
+
db_manager = DatabaseManager(db_uri, register_pgvector=False)
|
|
43
|
+
self._db_connections[db_name] = db_manager
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
def get_database_manager(self, db_name: str) -> DatabaseManager:
|
|
46
|
+
"""
|
|
47
|
+
Retrieves a registered DatabaseManager instance from the cache.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
return self._db_connections[db_name]
|
|
51
|
+
except KeyError:
|
|
52
|
+
logging.error(f"Attempted to access unregistered database: '{db_name}'")
|
|
53
|
+
raise IAToolkitException(
|
|
54
|
+
IAToolkitException.ErrorType.DATABASE_ERROR,
|
|
55
|
+
f"Database '{db_name}' is not registered with the SqlService."
|
|
56
|
+
)
|
|
34
57
|
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
def exec_sql(self, db_name: str, sql_statement: str) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Executes a raw SQL statement against a registered database and returns the result as a JSON string.
|
|
37
61
|
"""
|
|
38
62
|
try:
|
|
39
|
-
#
|
|
40
|
-
|
|
63
|
+
# 1. Get the database manager from the cache
|
|
64
|
+
db_manager = self.get_database_manager(db_name)
|
|
41
65
|
|
|
42
|
-
#
|
|
66
|
+
# 2. Execute the SQL statement
|
|
67
|
+
result = db_manager.get_session().execute(text(sql_statement))
|
|
43
68
|
cols = result.keys()
|
|
44
|
-
|
|
45
|
-
# convert rows to dict
|
|
46
69
|
rows_context = [dict(zip(cols, row)) for row in result.fetchall()]
|
|
47
70
|
|
|
48
|
-
#
|
|
71
|
+
# seialize the result
|
|
49
72
|
sql_result_json = json.dumps(rows_context, default=self.util.serialize)
|
|
50
73
|
|
|
51
74
|
return sql_result_json
|
|
75
|
+
except IAToolkitException:
|
|
76
|
+
# Re-raise exceptions from get_database_manager to preserve the specific error
|
|
77
|
+
raise
|
|
52
78
|
except Exception as e:
|
|
53
|
-
|
|
79
|
+
# Attempt to rollback if a session was active
|
|
80
|
+
db_manager = self._db_connections.get(db_name)
|
|
81
|
+
if db_manager:
|
|
82
|
+
db_manager.get_session().rollback()
|
|
54
83
|
|
|
55
84
|
error_message = str(e)
|
|
56
85
|
if 'timed out' in str(e):
|
|
57
|
-
error_message = '
|
|
86
|
+
error_message = self.i18n_service.t('errors.timeout')
|
|
58
87
|
|
|
88
|
+
logging.error(f"Error executing SQL statement: {error_message}")
|
|
59
89
|
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
|
|
60
90
|
error_message) from e
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
from iatoolkit.repositories.models import UserFeedback, Company
|
|
7
7
|
from injector import inject
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
9
10
|
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
10
|
-
from iatoolkit.infra.mail_app import MailApp
|
|
11
|
+
from iatoolkit.infra.mail_app import MailApp
|
|
11
12
|
import logging
|
|
12
13
|
|
|
13
14
|
|
|
@@ -15,9 +16,11 @@ class UserFeedbackService:
|
|
|
15
16
|
@inject
|
|
16
17
|
def __init__(self,
|
|
17
18
|
profile_repo: ProfileRepo,
|
|
19
|
+
i18n_service: I18nService,
|
|
18
20
|
google_chat_app: GoogleChatApp,
|
|
19
21
|
mail_app: MailApp):
|
|
20
22
|
self.profile_repo = profile_repo
|
|
23
|
+
self.i18n_service = i18n_service
|
|
21
24
|
self.google_chat_app = google_chat_app
|
|
22
25
|
self.mail_app = mail_app
|
|
23
26
|
|
|
@@ -31,9 +34,9 @@ class UserFeedbackService:
|
|
|
31
34
|
}
|
|
32
35
|
chat_result = self.google_chat_app.send_message(message_data=chat_data)
|
|
33
36
|
if not chat_result.get('success'):
|
|
34
|
-
logging.warning(f"
|
|
37
|
+
logging.warning(f"error sending notification to Google Chat: {chat_result.get('message')}")
|
|
35
38
|
except Exception as e:
|
|
36
|
-
logging.exception(f"
|
|
39
|
+
logging.exception(f"error sending notification to Google Chat: {e}")
|
|
37
40
|
|
|
38
41
|
def _send_email_notification(self, destination_email: str, company_name: str, message_text: str):
|
|
39
42
|
"""Envía una notificación de feedback por correo electrónico."""
|
|
@@ -43,20 +46,20 @@ class UserFeedbackService:
|
|
|
43
46
|
html_body = message_text.replace('\n', '<br>')
|
|
44
47
|
self.mail_app.send_email(to=destination_email, subject=subject, body=html_body)
|
|
45
48
|
except Exception as e:
|
|
46
|
-
logging.exception(f"
|
|
49
|
+
logging.exception(f"error sending email de feedback: {e}")
|
|
47
50
|
|
|
48
51
|
def _handle_notification(self, company: Company, message_text: str):
|
|
49
52
|
"""Lee la configuración de la empresa y envía la notificación al canal correspondiente."""
|
|
50
53
|
feedback_params = company.parameters.get('user_feedback')
|
|
51
54
|
if not isinstance(feedback_params, dict):
|
|
52
|
-
logging.warning(f"
|
|
55
|
+
logging.warning(f"missing 'user_feedback' configuration for company: {company.short_name}.")
|
|
53
56
|
return
|
|
54
57
|
|
|
55
58
|
# get channel and destination
|
|
56
59
|
channel = feedback_params.get('channel')
|
|
57
60
|
destination = feedback_params.get('destination')
|
|
58
61
|
if not channel or not destination:
|
|
59
|
-
logging.warning(f"
|
|
62
|
+
logging.warning(f"invalid 'user_feedback' configuration for: {company.short_name}. Faltan 'channel' o 'destination'.")
|
|
60
63
|
return
|
|
61
64
|
|
|
62
65
|
if channel == 'google_chat':
|
|
@@ -64,7 +67,7 @@ class UserFeedbackService:
|
|
|
64
67
|
elif channel == 'email':
|
|
65
68
|
self._send_email_notification(destination_email=destination, company_name=company.short_name, message_text=message_text)
|
|
66
69
|
else:
|
|
67
|
-
logging.warning(f"
|
|
70
|
+
logging.warning(f"unknown feedback channel: '{channel}' for company {company.short_name}.")
|
|
68
71
|
|
|
69
72
|
def new_feedback(self,
|
|
70
73
|
company_short_name: str,
|
|
@@ -72,19 +75,18 @@ class UserFeedbackService:
|
|
|
72
75
|
user_identifier: str,
|
|
73
76
|
rating: int = None) -> dict:
|
|
74
77
|
try:
|
|
75
|
-
# 1. Validar empresa
|
|
76
78
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
77
79
|
if not company:
|
|
78
|
-
return {'error':
|
|
80
|
+
return {'error': self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
|
|
79
81
|
|
|
80
|
-
# 2.
|
|
82
|
+
# 2. send notification using company configuration
|
|
81
83
|
notification_text = (f"*Nuevo feedback de {company_short_name}*:\n"
|
|
82
84
|
f"*Usuario:* {user_identifier}\n"
|
|
83
85
|
f"*Mensaje:* {message}\n"
|
|
84
86
|
f"*Calificación:* {rating if rating is not None else 'N/A'}")
|
|
85
87
|
self._handle_notification(company, notification_text)
|
|
86
88
|
|
|
87
|
-
# 3.
|
|
89
|
+
# 3. always save the feedback in the database
|
|
88
90
|
new_feedback_obj = UserFeedback(
|
|
89
91
|
company_id=company.id,
|
|
90
92
|
message=message,
|
|
@@ -93,10 +95,10 @@ class UserFeedbackService:
|
|
|
93
95
|
)
|
|
94
96
|
saved_feedback = self.profile_repo.save_feedback(new_feedback_obj)
|
|
95
97
|
if not saved_feedback:
|
|
96
|
-
logging.error(f"
|
|
97
|
-
return {'error': '
|
|
98
|
+
logging.error(f"can't save feedback for user {user_identifier}/{company_short_name}")
|
|
99
|
+
return {'error': 'can not save the feedback'}
|
|
98
100
|
|
|
99
|
-
return {'message': 'Feedback guardado correctamente'}
|
|
101
|
+
return {'success': True, 'message': 'Feedback guardado correctamente'}
|
|
100
102
|
|
|
101
103
|
except Exception as e:
|
|
102
104
|
logging.exception(f"Error crítico en el servicio de feedback: {e}")
|
|
@@ -1,110 +1,80 @@
|
|
|
1
1
|
$(document).ready(function () {
|
|
2
|
+
const feedbackModal = $('#feedbackModal');
|
|
3
|
+
$('#submit-feedback').on('click', function () {
|
|
4
|
+
sendFeedback(this);
|
|
5
|
+
});
|
|
2
6
|
|
|
3
7
|
// Evento para enviar el feedback
|
|
4
|
-
|
|
8
|
+
async function sendFeedback(submitButton) {
|
|
9
|
+
toastr.options = {"positionClass": "toast-bottom-right", "preventDuplicates": true};
|
|
5
10
|
const feedbackText = $('#feedback-text').val().trim();
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
// --- LÓGICA DE COMPATIBILIDAD BS3 / BS5 ---
|
|
9
|
-
// Detecta si Bootstrap 5 está presente.
|
|
10
|
-
const isBootstrap5 = (typeof bootstrap !== 'undefined');
|
|
11
|
-
|
|
12
|
-
// Define el HTML del botón de cierre según la versión.
|
|
13
|
-
const closeButtonHtml = isBootstrap5 ?
|
|
14
|
-
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' : // Versión BS5
|
|
15
|
-
'<button type="button" class="close" data-dismiss="alert"><span>×</span></button>'; // Versión BS3/BS4
|
|
16
|
-
// --- FIN DE LA LÓGICA DE COMPATIBILIDAD ---
|
|
11
|
+
const activeStars = $('.star.active').length;
|
|
17
12
|
|
|
18
13
|
if (!feedbackText) {
|
|
19
|
-
|
|
20
|
-
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
|
21
|
-
<strong>¡Atención!</strong> Por favor, escribe tu comentario antes de enviar.
|
|
22
|
-
${closeButtonHtml}
|
|
23
|
-
</div>`;
|
|
24
|
-
$('.modal-body .alert').remove();
|
|
25
|
-
$('.modal-body').prepend(alertHtml);
|
|
14
|
+
toastr.error(t_js('feedback_comment_error'));
|
|
26
15
|
return;
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
const activeStars = $('.star.active').length;
|
|
30
18
|
if (activeStars === 0) {
|
|
31
|
-
|
|
32
|
-
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
|
33
|
-
<strong>¡Atención!</strong> Por favor, califica al asistente con las estrellas.
|
|
34
|
-
${closeButtonHtml}
|
|
35
|
-
</div>`;
|
|
36
|
-
$('.modal-body .alert').remove();
|
|
37
|
-
$('.modal-body').prepend(alertHtml);
|
|
19
|
+
toastr.error(t_js('feedback_rating_error'));
|
|
38
20
|
return;
|
|
39
21
|
}
|
|
40
22
|
|
|
41
|
-
submitButton.
|
|
42
|
-
submitButton.html('<i class="bi bi-send me-1 icon-spaced"></i>Enviando...');
|
|
23
|
+
submitButton.disabled = true;
|
|
43
24
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
// call the IAToolkit API to send feedback
|
|
26
|
+
const data = {
|
|
27
|
+
"user_identifier": window.user_identifier,
|
|
28
|
+
"message": feedbackText,
|
|
29
|
+
"rating": activeStars,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const responseData = await callToolkit('/api/feedback', data, "POST");
|
|
33
|
+
if (responseData)
|
|
34
|
+
toastr.success(t_js('feedback_sent_success_body'), t_js('feedback_sent_success_title'));
|
|
48
35
|
else
|
|
49
|
-
toastr.error('
|
|
50
|
-
});
|
|
36
|
+
toastr.error(t_js('feedback_sent_error'));
|
|
51
37
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
$('#submit-feedback').html('<i class="bi bi-send me-1 icon-spaced"></i>Enviar');
|
|
56
|
-
$('.star').removeClass('active hover-active'); // Resetea estrellas
|
|
57
|
-
$('#feedback-text').val(''); // Limpia texto
|
|
58
|
-
$('.modal-body .alert').remove(); // Quita alertas previas
|
|
59
|
-
$('#feedbackModal').modal('show');
|
|
60
|
-
});
|
|
38
|
+
submitButton.disabled = false;
|
|
39
|
+
feedbackModal.modal('hide');
|
|
40
|
+
}
|
|
61
41
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
42
|
+
// Evento para abrir el modal de feedback
|
|
43
|
+
$('#send-feedback-button').on('click', function () {
|
|
44
|
+
$('#submit-feedback').prop('disabled', false);
|
|
45
|
+
$('.star').removeClass('active hover-active'); // Resetea estrellas
|
|
46
|
+
$('#feedback-text').val('');
|
|
47
|
+
feedbackModal.modal('show');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Evento que se dispara DESPUÉS de que el modal se ha ocultado
|
|
51
|
+
$('#feedbackModal').on('hidden.bs.modal', function () {
|
|
52
|
+
$('#feedback-text').val('');
|
|
53
|
+
$('.star').removeClass('active');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Function for the star rating system
|
|
57
|
+
window.gfg = function (rating) {
|
|
58
|
+
$('.star').removeClass('active');
|
|
59
|
+
$('.star').each(function (index) {
|
|
60
|
+
if (index < rating) {
|
|
61
|
+
$(this).addClass('active');
|
|
62
|
+
}
|
|
67
63
|
});
|
|
64
|
+
};
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
$(
|
|
72
|
-
$('.star').
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
$('.star').hover(
|
|
67
|
+
function () {
|
|
68
|
+
const rating = $(this).data('rating');
|
|
69
|
+
$('.star').removeClass('hover-active');
|
|
70
|
+
$('.star').each(function (index) {
|
|
71
|
+
if ($(this).data('rating') <= rating) {
|
|
72
|
+
$(this).addClass('hover-active');
|
|
75
73
|
}
|
|
76
74
|
});
|
|
77
|
-
}
|
|
75
|
+
},
|
|
76
|
+
function () {
|
|
77
|
+
$('.star').removeClass('hover-active');
|
|
78
|
+
});
|
|
78
79
|
|
|
79
|
-
$('.star').hover(
|
|
80
|
-
function() {
|
|
81
|
-
const rating = $(this).data('rating');
|
|
82
|
-
$('.star').removeClass('hover-active');
|
|
83
|
-
$('.star').each(function(index) {
|
|
84
|
-
if ($(this).data('rating') <= rating) {
|
|
85
|
-
$(this).addClass('hover-active');
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
function() {
|
|
90
|
-
$('.star').removeClass('hover-active');
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
80
|
});
|
|
94
|
-
|
|
95
|
-
const sendFeedback = async function(message) {
|
|
96
|
-
const activeStars = $('.star.active').length;
|
|
97
|
-
const data = {
|
|
98
|
-
"user_identifier": window.user_identifier,
|
|
99
|
-
"message": message,
|
|
100
|
-
"rating": activeStars,
|
|
101
|
-
};
|
|
102
|
-
try {
|
|
103
|
-
// Asumiendo que callLLMAPI está definido globalmente en otro archivo (ej. chat_main.js)
|
|
104
|
-
const responseData = await callToolkit('/api/feedback', data, "POST");
|
|
105
|
-
return responseData;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error("Error al enviar feedback:", error);
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
$(document).ready(function () {
|
|
2
|
+
|
|
3
|
+
let helpContent = null; // Variable para cachear el contenido de ayuda
|
|
4
|
+
|
|
5
|
+
// Evento de clic en el botón de ayuda
|
|
6
|
+
$('#open-help-button').on('click', async function () {
|
|
7
|
+
const helpModal = new bootstrap.Modal(document.getElementById('helpModal'));
|
|
8
|
+
const accordionContainer = $('#help-accordion-container');
|
|
9
|
+
const spinner = $('#help-spinner');
|
|
10
|
+
|
|
11
|
+
// Si el contenido no se ha cargado, hacer la llamada a la API
|
|
12
|
+
if (helpContent) {
|
|
13
|
+
// Si el contenido ya está cacheado, solo muestra el modal
|
|
14
|
+
helpModal.show();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
spinner.show();
|
|
19
|
+
accordionContainer.hide();
|
|
20
|
+
helpModal.show();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const helpContent = await callToolkit('/api/help-content', {}, "POST");
|
|
24
|
+
|
|
25
|
+
if (!helpContent) {
|
|
26
|
+
toastr.error('No se pudo cargar la guía de uso. Por favor, intente más tarde.');
|
|
27
|
+
spinner.hide();
|
|
28
|
+
helpModal.hide();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Construir el HTML del acordeón y mostrarlo
|
|
33
|
+
buildHelpAccordion(helpContent);
|
|
34
|
+
spinner.hide();
|
|
35
|
+
accordionContainer.show();
|
|
36
|
+
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Error al cargar el contenido de ayuda:", error);
|
|
39
|
+
toastr.error('Ocurrió un error de red al cargar la guía.');
|
|
40
|
+
spinner.hide();
|
|
41
|
+
helpModal.hide();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Construye dinámicamente el HTML para el acordeón de ayuda a partir de los datos.
|
|
47
|
+
* @param {object} data El objeto JSON con el contenido de ayuda.
|
|
48
|
+
*/
|
|
49
|
+
function buildHelpAccordion(data) {
|
|
50
|
+
const container = $('#help-accordion-container');
|
|
51
|
+
container.empty(); // Limpiar cualquier contenido previo
|
|
52
|
+
|
|
53
|
+
let accordionHtml = '';
|
|
54
|
+
|
|
55
|
+
if (data.example_questions) {
|
|
56
|
+
let contentHtml = '';
|
|
57
|
+
data.example_questions.forEach(cat => {
|
|
58
|
+
contentHtml += `<h6 class="fw-bold">${cat.category}</h6><ul>`;
|
|
59
|
+
cat.questions.forEach(q => contentHtml += `<li>${q}</li>`);
|
|
60
|
+
contentHtml += `</ul>`;
|
|
61
|
+
});
|
|
62
|
+
accordionHtml += createAccordionItem('examples', 'Preguntas de Ejemplo', contentHtml, true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (data.data_sources) {
|
|
66
|
+
let contentHtml = '<dl>';
|
|
67
|
+
data.data_sources.forEach(p => {
|
|
68
|
+
contentHtml += `<dt>${p.source}</dt><dd>${p.description}</dd>`;
|
|
69
|
+
});
|
|
70
|
+
contentHtml += `</dl>`;
|
|
71
|
+
accordionHtml += createAccordionItem('sources', 'Datos disponibles', contentHtml );
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (data.best_practices) {
|
|
75
|
+
let contentHtml = '<dl>';
|
|
76
|
+
data.best_practices.forEach(p => {
|
|
77
|
+
contentHtml += `<dt>${p.title}</dt><dd>${p.description}`;
|
|
78
|
+
if (p.example) {
|
|
79
|
+
contentHtml += `<br><small class="text-muted"><em>Ej: "${p.example}"</em></small>`;
|
|
80
|
+
}
|
|
81
|
+
contentHtml += `</dd>`;
|
|
82
|
+
});
|
|
83
|
+
contentHtml += `</dl>`;
|
|
84
|
+
accordionHtml += createAccordionItem('practices', 'Mejores Prácticas', contentHtml);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (data.capabilities) {
|
|
88
|
+
let contentHtml = `<div class="row">`;
|
|
89
|
+
contentHtml += `<div class="col-md-6"><h6 class="fw-bold">Puede hacer:</h6><ul>${data.capabilities.can_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
|
|
90
|
+
contentHtml += `<div class="col-md-6"><h6 class="fw-bold">No puede hacer:</h6><ul>${data.capabilities.cannot_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
|
|
91
|
+
contentHtml += `</div>`;
|
|
92
|
+
accordionHtml += createAccordionItem('capabilities', 'Capacidades y Límites', contentHtml);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
container.html(accordionHtml);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper para crear un item del acordeón de Bootstrap.
|
|
100
|
+
* @param {string} id El ID base para los elementos.
|
|
101
|
+
* @param {string} title El título que se muestra en el botón del acordeón.
|
|
102
|
+
* @param {string} contentHtml El HTML que va dentro del cuerpo colapsable.
|
|
103
|
+
* @param {boolean} isOpen Si el item debe estar abierto por defecto.
|
|
104
|
+
* @returns {string} El string HTML del item del acordeón.
|
|
105
|
+
*/
|
|
106
|
+
function createAccordionItem(id, title, contentHtml, isOpen = false) {
|
|
107
|
+
const showClass = isOpen ? 'show' : '';
|
|
108
|
+
const collapsedClass = isOpen ? '' : 'collapsed';
|
|
109
|
+
|
|
110
|
+
return `
|
|
111
|
+
<div class="accordion-item">
|
|
112
|
+
<h2 class="accordion-header" id="heading-${id}">
|
|
113
|
+
<button class="accordion-button ${collapsedClass}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-${id}" aria-expanded="${isOpen}" aria-controls="collapse-${id}">
|
|
114
|
+
${title}
|
|
115
|
+
</button>
|
|
116
|
+
</h2>
|
|
117
|
+
<div id="collapse-${id}" class="accordion-collapse collapse ${showClass}" aria-labelledby="heading-${id}" data-bs-parent="#help-accordion-container">
|
|
118
|
+
<div class="accordion-body">
|
|
119
|
+
${contentHtml}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>`;
|
|
123
|
+
}
|
|
124
|
+
});
|