iatoolkit 0.11.0__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.
Files changed (106) hide show
  1. iatoolkit/base_company.py +11 -3
  2. iatoolkit/common/routes.py +92 -52
  3. iatoolkit/common/session_manager.py +0 -1
  4. iatoolkit/common/util.py +17 -27
  5. iatoolkit/iatoolkit.py +91 -47
  6. iatoolkit/infra/llm_client.py +7 -8
  7. iatoolkit/infra/openai_adapter.py +1 -1
  8. iatoolkit/infra/redis_session_manager.py +48 -2
  9. iatoolkit/locales/en.yaml +144 -0
  10. iatoolkit/locales/es.yaml +140 -0
  11. iatoolkit/repositories/database_manager.py +17 -2
  12. iatoolkit/repositories/models.py +31 -4
  13. iatoolkit/repositories/profile_repo.py +7 -2
  14. iatoolkit/services/auth_service.py +193 -0
  15. iatoolkit/services/branding_service.py +59 -18
  16. iatoolkit/services/dispatcher_service.py +10 -40
  17. iatoolkit/services/excel_service.py +15 -15
  18. iatoolkit/services/help_content_service.py +30 -0
  19. iatoolkit/services/history_service.py +2 -11
  20. iatoolkit/services/i18n_service.py +104 -0
  21. iatoolkit/services/jwt_service.py +15 -24
  22. iatoolkit/services/language_service.py +77 -0
  23. iatoolkit/services/onboarding_service.py +43 -0
  24. iatoolkit/services/profile_service.py +148 -75
  25. iatoolkit/services/query_service.py +124 -81
  26. iatoolkit/services/tasks_service.py +1 -1
  27. iatoolkit/services/user_feedback_service.py +68 -32
  28. iatoolkit/services/user_session_context_service.py +112 -54
  29. iatoolkit/static/images/fernando.jpeg +0 -0
  30. iatoolkit/static/js/chat_feedback_button.js +80 -0
  31. iatoolkit/static/js/chat_help_content.js +124 -0
  32. iatoolkit/static/js/chat_history_button.js +112 -0
  33. iatoolkit/static/js/chat_logout_button.js +36 -0
  34. iatoolkit/static/js/chat_main.js +148 -220
  35. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  36. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  37. iatoolkit/static/js/chat_reload_button.js +35 -0
  38. iatoolkit/static/styles/chat_iatoolkit.css +367 -199
  39. iatoolkit/static/styles/chat_modal.css +98 -76
  40. iatoolkit/static/styles/chat_public.css +107 -0
  41. iatoolkit/static/styles/landing_page.css +182 -0
  42. iatoolkit/static/styles/onboarding.css +169 -0
  43. iatoolkit/system_prompts/query_main.prompt +3 -12
  44. iatoolkit/templates/_company_header.html +20 -0
  45. iatoolkit/templates/_login_widget.html +42 -0
  46. iatoolkit/templates/base.html +40 -20
  47. iatoolkit/templates/change_password.html +57 -36
  48. iatoolkit/templates/chat.html +169 -83
  49. iatoolkit/templates/chat_modals.html +134 -68
  50. iatoolkit/templates/error.html +44 -8
  51. iatoolkit/templates/forgot_password.html +40 -23
  52. iatoolkit/templates/index.html +145 -0
  53. iatoolkit/templates/login_simulation.html +34 -0
  54. iatoolkit/templates/onboarding_shell.html +104 -0
  55. iatoolkit/templates/signup.html +63 -65
  56. iatoolkit/views/base_login_view.py +92 -0
  57. iatoolkit/views/change_password_view.py +56 -30
  58. iatoolkit/views/external_login_view.py +61 -28
  59. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  60. iatoolkit/views/forgot_password_view.py +27 -19
  61. iatoolkit/views/help_content_api_view.py +54 -0
  62. iatoolkit/views/history_api_view.py +56 -0
  63. iatoolkit/views/home_view.py +50 -23
  64. iatoolkit/views/index_view.py +14 -0
  65. iatoolkit/views/init_context_api_view.py +73 -0
  66. iatoolkit/views/llmquery_api_view.py +57 -0
  67. iatoolkit/views/login_simulation_view.py +81 -0
  68. iatoolkit/views/login_view.py +130 -37
  69. iatoolkit/views/logout_api_view.py +49 -0
  70. iatoolkit/views/profile_api_view.py +46 -0
  71. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
  72. iatoolkit/views/signup_view.py +42 -35
  73. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  74. iatoolkit/views/tasks_review_api_view.py +55 -0
  75. iatoolkit/views/user_feedback_api_view.py +60 -0
  76. iatoolkit/views/verify_user_view.py +35 -28
  77. {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
  78. iatoolkit-0.66.2.dist-info/RECORD +119 -0
  79. iatoolkit/common/auth.py +0 -200
  80. iatoolkit/static/images/arrow_up.png +0 -0
  81. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  82. iatoolkit/static/images/logo_clinica.png +0 -0
  83. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  84. iatoolkit/static/images/logo_maxxa.png +0 -0
  85. iatoolkit/static/images/logo_notaria.png +0 -0
  86. iatoolkit/static/images/logo_tarjeta.png +0 -0
  87. iatoolkit/static/images/logo_umayor.png +0 -0
  88. iatoolkit/static/images/upload.png +0 -0
  89. iatoolkit/static/js/chat_feedback.js +0 -115
  90. iatoolkit/static/js/chat_history.js +0 -117
  91. iatoolkit/static/styles/chat_info.css +0 -53
  92. iatoolkit/templates/header.html +0 -31
  93. iatoolkit/templates/home.html +0 -199
  94. iatoolkit/templates/login.html +0 -43
  95. iatoolkit/templates/test.html +0 -9
  96. iatoolkit/views/chat_token_request_view.py +0 -98
  97. iatoolkit/views/chat_view.py +0 -58
  98. iatoolkit/views/download_file_view.py +0 -58
  99. iatoolkit/views/external_chat_login_view.py +0 -95
  100. iatoolkit/views/history_view.py +0 -57
  101. iatoolkit/views/llmquery_view.py +0 -65
  102. iatoolkit/views/tasks_review_view.py +0 -83
  103. iatoolkit/views/user_feedback_view.py +0 -74
  104. iatoolkit-0.11.0.dist-info/RECORD +0 -110
  105. {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
  106. {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,20 @@
1
+ {# El div principal ahora es un contenedor y tiene los estilos y clases de alineación #}
2
+ <div class="custom-company-header container d-flex justify-content-between align-items-center">
3
+
4
+ {% if company_short_name and branding %}
5
+ <a href="{{ url_for('home', company_short_name=company_short_name) }}"
6
+ class="brand-name"
7
+ style="{{ branding.primary_text_style }}">
8
+ {{ branding.name }} IA
9
+ </a>
10
+ {% else %}
11
+ <span class="brand-name">
12
+ IAToolkit
13
+ </span>
14
+ {% endif %}
15
+
16
+ {# Texto "Powered by" con enlace a iatoolkit.com #}
17
+ <span class="powered-by">
18
+ Powered by <a href="{{ url_for('index') }}" rel="noopener noreferrer" class="iatoolkit-link">IAToolkit</a>
19
+ </span>
20
+ </div>
@@ -0,0 +1,42 @@
1
+ <div class="branded-form-container">
2
+ <!-- 1. Encabezado de Marketing -->
3
+ <div class="text-center mb-4">
4
+ <p class="text-muted widget-intro-text">
5
+ {{ t('ui.login_widget.welcome_message') }}
6
+ </p>
7
+ </div>
8
+
9
+ <!-- 2. Formulario de Inicio de Sesión -->
10
+ <form id="login-form"
11
+ action="{{ url_for('login', company_short_name=company_short_name) }}"
12
+ method="post">
13
+ <div class="mb-3">
14
+ <label for="email" class="form-label d-block">{{ t('ui.signup.email_label') }}</label>
15
+ <input type="email" id="email" name="email" class="form-control"
16
+ required value="{{ form_data.email if form_data is defined else '' }}">
17
+ </div>
18
+ <div class="mb-3">
19
+ <label for="password" class="form-label d-block">{{ t('ui.signup.password_label') }}</label>
20
+ <input type="password" id="password" name="password"
21
+ class="form-control" required>
22
+ </div>
23
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2">
24
+ {{ t('ui.login_widget.login_button') }}
25
+ </button>
26
+ </form>
27
+
28
+ <!-- 3. Nueva Sección de Registro más Atractiva -->
29
+ <div class="mt-4 pt-3 text-center" style="border-top: 1px solid #e0e0e0;">
30
+ <span class="text-muted small">{{ t('ui.login_widget.no_account_prompt') }}</span>
31
+ <a href="{{ url_for('signup', company_short_name=company_short_name) }}" id="signup-link"
32
+ class="fw-bold ms-1 text-decoration-none" style="color: var(--brand-primary-color);">
33
+ {{ t('ui.login_widget.signup_link') }}</a>
34
+ </div>
35
+
36
+ <!-- 4. Enlace de Recuperación de Contraseña (más sutil) -->
37
+ <div class="text-center mt-2">
38
+ <a href="{{ url_for('forgot_password', company_short_name=company_short_name) }}" class="text-decoration-none text-muted" style="font-size: 0.8rem;">
39
+ {{ t('ui.login_widget.forgot_password_link') }}
40
+ </a>
41
+ </div>
42
+ </div>
@@ -4,39 +4,59 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>{% block title %}Chatbot{% endblock %}</title>
7
+
7
8
  <!-- Bootstrap 5 CSS -->
8
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
9
- <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" >
10
11
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/filepond/dist/filepond.min.css">
11
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
12
- <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_iatoolkit.css') }}">
13
- <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_modal.css') }}">
14
- <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css') }}">
13
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" />
14
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_iatoolkit.css', _external=True) }}">
15
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/onboarding.css', _external=True) }}">
16
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_modal.css', _external=True) }}">
17
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}">
18
+
19
+ {% block styles %}{% endblock %}
15
20
  </head>
16
- <body class="d-flex flex-column p-3" style="min-height: 100vh;">
17
- <main class="d-flex flex-column flex-grow-1">
21
+ <body>
22
+ <!-- El "Ancla": Envolvemos el contenido en un div con un ID y estilo. -->
23
+ <div id="page-content-wrapper" style="position: relative;">
18
24
  {% block content %}{% endblock %}
19
- </main>
25
+ </div>
20
26
 
21
27
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
22
28
  <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
23
29
  <script src="https://cdn.jsdelivr.net/npm/filepond/dist/filepond.min.js"></script>
24
30
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
25
-
31
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
26
32
 
27
33
  <!-- Mostrar alertas SweetAlert2 si existe el mensaje -->
28
34
  <script>
29
- {% if alert_message %}
30
- Swal.fire({
31
- text: "{{ alert_message }}", // Mensaje pasado desde Flask
32
- icon: "{{ alert_icon | default('error') }}", // Ícono por defecto "error"
33
- confirmButtonText: "OK",
34
- cancelButtonText: 'Cancelar',
35
- customClass: {
36
- confirmButton: 'custom-confirm-button', // Clase personalizada para el botón
37
- cancelButton: 'custom-cancel-button' // Clase personalizada para el botón de cancelar
38
- }
39
- });
35
+ // Configuración global de Toastr
36
+ toastr.options = {
37
+ "closeButton": true,
38
+ "progressBar": true,
39
+ "positionClass": "toast-bottom-right",
40
+ "preventDuplicates": true,
41
+ "timeOut": "7000",
42
+ "extendedTimeOut": "1000",
43
+ "tapToDismiss": false,
44
+ "target": "#page-content-wrapper"
45
+ };
46
+
47
+ {% if flashed_messages %}
48
+ {% for category, message in flashed_messages %}
49
+ var toastClass = 'toast-info'; // default class
50
+ if ('{{ category }}' === 'error') {
51
+ toastClass = 'toast-error';
52
+ } else if ('{{ category }}' === 'success') {
53
+ toastClass = 'toast-success';
54
+ }
55
+
56
+ // Llama a Toastr usando la opción 'toastClass' para aplicar nuestro estilo
57
+ toastr.info("{{ message }}", null, { "toastClass": "toast " + toastClass });
58
+
59
+ {% endfor %}
40
60
  {% endif %}
41
61
  </script>
42
62
 
@@ -1,45 +1,66 @@
1
1
  {% extends "base.html" %}
2
2
 
3
- {% block title %}Cambiar Contraseña{% endblock %}
3
+ {% block title %}{{ t('ui.change_password.title') }} - {{ company.name }}{% endblock %}
4
+
5
+ {% block styles %}
6
+ <style>
7
+ {{ branding.css_variables | safe }}
8
+ </style>
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_public.css') }}">
10
+ {% endblock %}
4
11
 
5
12
  {% block content %}
6
- <div class="container vh-100 d-flex justify-content-center align-items-center">
7
- <div class="col-11 col-md-8 col-lg-5 border rounded p-3 shadow-sm">
8
- <h4 class="text-muted fw-semibold text-start mb-3">Cambiar Contraseña</h4>
9
- <h6 class="text-muted text-start mb-4">{{ email }}</h6>
10
-
11
- <form action="{{ url_for('change_password', company_short_name=company_short_name, token=token) }}" method="post">
12
- <div class="mb-3">
13
- <label for="temp_code" class="form-label text-muted">Código Temporal recibido</label>
14
- <input type="text" name="temp_code" id="temp_code"
15
- class="form-control text-muted" required
16
- autocomplete="off"
17
- value="{{ form_data.temp_code if form_data else '' }}">
18
- </div>
19
- <div class="mb-3">
20
- <label for="new_password" class="form-label text-muted">Nueva Contraseña</label>
21
- <input type="password" name="new_password" id="new_password"
22
- class="form-control text-muted" required
23
- value="{{ form_data.new_password if form_data else '' }}">
24
- <small class="form-text text-muted">
25
- La contraseña debe contener al menos 8 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial.
26
- </small>
13
+ <div class="container mt-4">
27
14
 
28
- </div>
29
- <div class="mb-3">
30
- <label for="confirm_password" class="form-label text-muted">Confirmar Nueva Contraseña</label>
31
- <input type="password" name="confirm_password" id="confirm_password"
32
- class="form-control text-muted" required
33
- value="{{ form_data.password if form_data else '' }}">
34
- </div>
35
- <button type="submit" class="btn btn-primary w-100">Cambiar Contraseña</button>
15
+ {% include '_company_header.html' %}
16
+
17
+ <!-- Sección contenedora para centrar el contenido -->
18
+ <section class="hero-section">
19
+ <div class="container">
20
+ <div class="row justify-content-center">
21
+ <div class="col-lg-6 col-md-8">
22
+ <div class="branded-form-container">
23
+ <h4 class="branded-form-title">{{ t('ui.change_password.title') }}</h4>
24
+ <p class="text-muted text-center mb-4">
25
+ {{ t('ui.change_password.subtitle', email=email) | safe }}
26
+ </p>
27
+
28
+ <form action="{{ url_for('change_password', company_short_name=company_short_name, token=token) }}" method="post">
36
29
 
37
- <p class="text-muted text-start mt-3" style="text-align: justify;">
38
- Ingresa el código de seguridad que recibiste por correo y elige una nueva contraseña.
39
- Asegúrate de que sea segura y fácil de recordar.
40
- </p>
30
+ <div class="mb-3">
31
+ <label for="temp_code" class="form-label text-secondary">{{ t('ui.change_password.temp_code_label') }}</label>
32
+ <input type="text" id="temp_code" name="temp_code" class="form-control"
33
+ required value="{{ form_data.temp_code if form_data else '' }}"
34
+ placeholder="{{ t('ui.change_password.temp_code_placeholder') }}">
35
+ </div>
41
36
 
42
- </form>
43
- </div>
37
+ <div class="mb-3">
38
+ <label for="new_password" class="form-label text-secondary">{{ t('ui.change_password.new_password_label') }}</label>
39
+ <input type="password" id="new_password" name="new_password" class="form-control" required>
40
+ <div class="d-flex align-items-start text-muted mt-2" style="font-size: 0.8rem;">
41
+ <i class="bi bi-info-circle me-2" style="font-size: 0.9rem; line-height: 1.4;"></i>
42
+ <span>{{ t('ui.change_password.password_instructions') }}</span>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="mb-3">
47
+ <label for="confirm_password" class="form-label text-secondary">{{ t('ui.change_password.confirm_password_label') }}</label>
48
+ <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
49
+ </div>
50
+
51
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.change_password.save_button') }}</button>
52
+ </form>
53
+
54
+ <div class="text-center mt-4 pt-3" style="border-top: 1px solid #e0e0e0;">
55
+ <a href="{{ url_for('home', company_short_name=company_short_name) }}" class="text-muted text-decoration-none fw-semibold">
56
+ <i class="bi bi-arrow-left me-1"></i>{{ t('ui.change_password.back_to_home') }}
57
+ </a>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </section>
44
64
  </div>
65
+
45
66
  {% endblock %}
@@ -1,66 +1,100 @@
1
1
  {% extends "base.html" %}
2
2
 
3
- {% block title %}IAToolkit{% endblock %}
3
+ {% block title %}{{ branding.name }} IA{% endblock %}
4
4
 
5
5
  {% block content %}
6
+ <div class="chat-layout-container container">
7
+ {% block styles %}
8
+ {# Movemos los estilos y los links aquí para que se rendericen en el <head> #}
9
+ <style>
10
+ {{ branding.css_variables | safe }}
11
+ </style>
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
6
14
 
7
- <style>
8
- {{ branding.css_variables | safe }}
9
- </style>
15
+ {% endblock %}
10
16
 
11
17
  <!-- Sección de encabezado con el usuario conectado -->
12
- <div id="company-section" class="company-section d-flex justify-content-between align-items-center px-3 py-2"
13
- style="{{ branding.header_style }}">
14
-
15
- <!-- Izquierda: Nombre de la Empresa y atribución "Powered by" -->
16
- <div class="d-flex align-items-center">
17
- <span style="{{ branding.primary_text_style }}">
18
- {{ branding.name }}
19
- </span>
20
- <span class="ms-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Powered by IAToolkit">
21
- <i class="bi bi-info-circle" style="color: {{ branding.header_text_color }}; opacity: 0.7; font-size: 0.9rem;"></i>
22
- </span>
23
- </div>
18
+ <div id="company-section"
19
+ class="company-section d-flex flex-column flex-md-row justify-content-md-between align-items-center px-3 py-2"
20
+ style="{{ branding.header_style }}">
24
21
 
25
- <!-- Derecha: Grupo de información y acciones para el usuario -->
26
- <div class="d-flex align-items-center">
27
- <!-- 1. ID de Usuario -->
28
- <span style="{{ branding.secondary_text_style }}">
29
- {{ external_user_id or user.email }}
30
- </span>
31
-
32
- <!-- 2. Separador Vertical -->
33
- <div class="vr mx-3"></div>
34
-
35
- <!-- 3. Iconos de Acción -->
36
- <a href="javascript:void(0);" id="history-button"
37
- class="action-icon-style" title="Historial con mis consultas" style="color: {{ branding.header_text_color }};">
38
- <i class="bi bi-clock-history"></i>
39
- </a>
40
- <a href="javascript:void(0);" id="send-feedback-button"
41
- class="ms-3 action-icon-style" title="Tu feedback es muy importante" style="color: {{ branding.header_text_color }};">
42
- <i class="bi bi-emoji-smile"></i>
43
- </a>
44
-
45
- <!-- Icono de cerrar sesión (al final) -->
46
- {% if user.email %}
47
- <a href="{{ url_for('logout', company_short_name=company_short_name) }}"
48
- class="ms-3 action-icon-style" title="Cerrar sesión" style="color: {{ branding.header_text_color }} !important;">
49
- <i class="bi bi-box-arrow-right"></i>
50
- </a>
51
- {% endif %}
52
- </div>
22
+ <!-- Fila 1 (Móvil) / Columna Izquierda (Desktop): Nombre de la Empresa -->
23
+ <div class="d-flex align-items-center mb-2 mb-md-0">
24
+ <span style="{{ branding.primary_text_style }}">
25
+ {{ branding.name }}
26
+ </span>
27
+ <span class="ms-2" data-bs-toggle="tooltip" data-bs-placement="bottom"
28
+ title="Powered by IAToolkit ({{ iatoolkit_version }})">
29
+ <i class="bi bi-info-circle" style="color: {{ branding.header_text_color }}; opacity: 0.7; font-size: 0.9rem;"></i>
30
+ </span>
31
+ </div>
53
32
 
54
- </div>
33
+ <!-- Contenedor para la derecha que agrupa ID de usuario y botones -->
34
+ <div class="d-flex flex-column flex-md-row align-items-center">
35
+
36
+ <!-- Fila 2 (Móvil) / Parte 1 de la Columna Derecha (Desktop): ID de Usuario -->
37
+ <span style="{{ branding.secondary_text_style }}" class="mb-2 mb-md-0">
38
+ {{ user_identifier }}
39
+ </span>
40
+
41
+ <!-- Separador Vertical (Solo visible en Desktop) -->
42
+ <div class="vr mx-3 d-none d-md-block"></div>
43
+
44
+ <!-- Fila 3 (Móvil) / Parte 2 de la Columna Derecha (Desktop): Iconos de Acción -->
45
+ <div class="d-flex align-items-center">
46
+ <a href="javascript:void(0);"
47
+ id="history-button"
48
+ class="action-icon-style" title="{{ t('ui.tooltips.history') }}"
49
+ style="color: {{ branding.header_text_color }};">
50
+ <i class="bi bi-clock-history"></i>
51
+ </a>
52
+ <a href="javascript:void(0);"
53
+ id="force-reload-button"
54
+ class="ms-3 action-icon-style"
55
+ title="{{ t('ui.tooltips.reload_context') }}"
56
+ style="color: {{ branding.header_text_color }};">
57
+ <i class="bi bi-arrow-clockwise"></i>
58
+ </a>
59
+ <a href="javascript:void(0);"
60
+ id="send-feedback-button"
61
+ class="ms-3 action-icon-style"
62
+ title="{{ t('ui.tooltips.feedback') }}"
63
+ style="color: {{ branding.header_text_color }};">
64
+ <i class="bi bi-emoji-smile"></i>
65
+ </a>
66
+
67
+ <a href="javascript:void(0);"
68
+ id="onboarding-button"
69
+ class="ms-3 action-icon-style"
70
+ title="{{ t('ui.tooltips.onboarding') }}"
71
+ style="color: {{ branding.header_text_color }};">
72
+ <i class="bi bi-lightbulb"></i>
73
+ </a>
74
+ <a href="javascript:void(0);"
75
+ id="open-help-button"
76
+ class="ms-3 action-icon-style"
77
+ title="{{ t('ui.tooltips.usage_guide') }}"
78
+ style="color: {{ branding.header_text_color }};">
79
+ <i class="bi bi-question-circle-fill"></i>
80
+ </a>
81
+ <a href="javascript:void(0);"
82
+ id="logout-button"
83
+ class="ms-3 action-icon-style"
84
+ title="{{ t('ui.tooltips.logout') }}"
85
+ style="color: {{ branding.header_text_color }}; !important;">
86
+ <i class="bi bi-box-arrow-right"></i>
87
+ </a>
88
+ </div>
89
+ </div>
90
+ </div>
55
91
 
56
92
  <div id="chat-container"
57
93
  class="chat-block mt-2 flex-grow-1"
58
94
  style="overflow-y: auto;">
59
- <div id="chat-messages">
60
- <!-- Mensaje de bienvenida estático -->
61
- <div class="answer-section">
62
- ¡Hola! en que te puedo ayudar hoy?
63
- </div>
95
+ <!-- Mensaje de bienvenida -->
96
+ <div class="answer-section">
97
+ {{ t('ui.chat.welcome_message') }}
64
98
  </div>
65
99
  </div>
66
100
 
@@ -79,8 +113,8 @@
79
113
  <div class="{{ prompt_col_class }}">
80
114
  <div class="position-relative h-100">
81
115
  <div class="input-group dropup h-100">
82
- <button type="button" id="prompt-select-button" class="btn btn-light border dropdown-toggle w-100 text-start" data-bs-toggle="dropdown" aria-expanded="false">
83
- Prompts disponibles ....
116
+ <button type="button" id="prompt-select-button" class="btn prompt-select-button border dropdown-toggle w-100 text-start" data-bs-toggle="dropdown" aria-expanded="false">
117
+ {{ t('ui.chat.prompts_available') }} ....
84
118
  </button>
85
119
  <ul class="dropdown-menu dropdown-menu-soft w-100">
86
120
  {% if prompts and prompts.message %}
@@ -94,8 +128,11 @@
94
128
  data-custom-fields='{{ prompt.custom_fields | tojson }}'>
95
129
  {{ prompt.description }}
96
130
  </a>
97
- </li> {% endfor %}
98
- {% if not loop.last %}<li><hr class="dropdown-divider"></li>{% endif %}
131
+ </li>
132
+ {% endfor %}
133
+ {% if not loop.last %}
134
+ <li><hr class="dropdown-divider"></li>
135
+ {% endif %}
99
136
  {% endfor %}
100
137
  {% endif %}
101
138
  </ul>
@@ -117,34 +154,41 @@
117
154
  <div id="chat-input-bar" class="chat-input-bar d-flex align-items-center">
118
155
  <!-- Iconos de la izquierda -->
119
156
  <div class="d-flex align-items-center">
120
- <!-- BOTÓN PARA CONTROLAR EL COLLAPSE -->
121
- <a class="p-2" href="#prompt-assistant-collapse" data-bs-toggle="collapse" role="button" aria-expanded="false" aria-controls="prompt-assistant-collapse" title="Usar Asistente de Prompts">
157
+ <!-- varita magica -->
158
+ <a class="p-2" href="#prompt-assistant-collapse" data-bs-toggle="collapse" role="button"
159
+ aria-expanded="false" aria-controls="prompt-assistant-collapse"
160
+ title="{{ t('ui.tooltips.use_prompt_assistant') }}">
122
161
  <i class="bi bi-magic"></i>
123
162
  </a>
124
- <a class="p-2" href="javascript:void(0);" id="paperclip-button" title="Adjuntar archivos">
163
+ <a class="p-2" href="javascript:void(0);" id="paperclip-button"
164
+ title="{{ t('ui.tooltips.attach_files') }}">
125
165
  <i class="bi bi-plus-circle"></i>
126
166
  </a>
127
167
  <div id="view-files-button-container" style="display: none;">
128
- <a class="p-2" href="javascript:void(0);" id="view-files-button" title="Ver archivos adjuntos">
168
+ <a class="p-2" href="javascript:void(0);" id="view-files-button"
169
+ title="{{ t('ui.tooltips.view_attached_files') }}">
129
170
  <i class="bi bi-file-earmark-text"></i>
130
171
  </a>
131
172
  </div>
132
173
  </div>
133
174
 
134
175
  <!-- Textarea Central -->
135
- <textarea id="question" placeholder="Escribe tu consulta aquí..." class="form-control chat-textarea" style="resize: none;" rows="1"></textarea>
176
+ <textarea id="question" placeholder="{{ t('ui.chat.input_placeholder') }}"
177
+ class="form-control chat-textarea" style="resize: none;" rows="1"></textarea>
136
178
 
137
179
  <!-- Botón de Enviar a la derecha -->
138
180
  <div class="d-flex align-items-center">
139
181
  <div id="send-button-container">
140
182
  <a href="javascript:void(0);" id="send-button"
141
- class="p-2 send-button-icon" title="Enviar">
183
+ class="p-2 send-button-icon"
184
+ title="{{ t('ui.buttons.send') }}">
142
185
  <i class="bi bi-arrow-up-circle-fill"></i>
143
186
  </a>
144
187
  </div>
145
188
  <div id="stop-button-container" style="display: none;">
146
189
  <a href="javascript:void(0);" id="stop-button"
147
- class="p-2 text-danger" title="Detener">
190
+ class="p-2 text-danger"
191
+ title="{{ t('ui.buttons.stop') }}">
148
192
  <i class="bi bi-stop-circle-fill"></i>
149
193
  </a>
150
194
  </div>
@@ -155,41 +199,44 @@
155
199
  <!-- Incluir los modales desde un archivo externo -->
156
200
  {% include 'chat_modals.html' %}
157
201
 
202
+ </div>
158
203
  {% endblock %}
159
204
 
160
205
  {% block scripts %}
161
206
  <script>
162
207
  // --- Global Configuration from Backend ---
163
208
  window.companyShortName = "{{ company_short_name }}";
209
+ window.user_identifier = "{{ user_identifier }}";
210
+ window.redeemToken = {{ redeem_token | tojson | default('null') }};
164
211
  window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
165
- window.externalUserId = "{{ external_user_id }}";
166
212
  window.availablePrompts = {{ prompts.message | tojson }};
213
+ window.onboardingCards = {{ onboarding_cards | tojson }};
214
+ window.sendButtonColor = "{{ branding.send_button_color }}";
215
+
216
+ // JS translations helper
217
+ window.i18n = {{ js_translations | tojson }};
218
+ function t_js(key) {
219
+ return window.i18n[key] || key;
220
+ }
221
+
167
222
 
168
- {% if auth_method == 'jwt' and session_jwt %}
169
- // Store session JWT if it exists, defined in the same global scope
170
- window.sessionJWT = "{{ session_jwt }}";
171
- {% endif %}
172
223
  </script>
173
224
 
174
225
  <!-- Carga de los scripts JS externos después de definir las variables globales -->
175
- <script src="{{ url_for('static', filename='js/chat_filepond.js') }}"></script>
176
- <script src="{{ url_for('static', filename='js/chat_history.js') }}"></script>
177
- <script src="{{ url_for('static', filename='js/chat_feedback.js') }}"></script>
178
- <script src="{{ url_for('static', filename='js/chat_main.js') }}"></script>
226
+ <script src="{{ url_for('static', filename='js/chat_history_button.js', _external=True) }}"></script>
227
+ <script src="{{ url_for('static', filename='js/chat_reload_button.js', _external=True) }}"></script>
228
+ <script src="{{ url_for('static', filename='js/chat_feedback_button.js', _external=True) }}"></script>
229
+ <script src="{{ url_for('static', filename='js/chat_help_content.js', _external=True) }}"></script>
230
+ <script src="{{ url_for('static', filename='js/chat_onboarding_button.js', _external=True) }}"></script>
231
+ <script src="{{ url_for('static', filename='js/chat_logout_button.js', _external=True) }}"></script>
232
+ <script src="{{ url_for('static', filename='js/chat_prompt_manager.js', _external=True) }}"></script>
233
+ <script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
234
+ <script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
235
+
236
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
237
+
238
+ <script>
179
239
 
180
- <script>
181
- document.addEventListener('DOMContentLoaded', function() {
182
- const promptCollapse = document.getElementById('prompt-assistant-collapse');
183
- if (promptCollapse) {
184
- promptCollapse.addEventListener('shown.bs.collapse', function () {
185
- // Desplazar la ventana al final de la página para mantener visible el área de entrada.
186
- window.scrollTo({
187
- top: document.body.scrollHeight,
188
- behavior: 'smooth'
189
- });
190
- });
191
- }
192
- });
193
240
  document.addEventListener('DOMContentLoaded', function () {
194
241
  // Inicializar todos los tooltips de la página
195
242
  var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
@@ -197,5 +244,44 @@
197
244
  return new bootstrap.Tooltip(tooltipTriggerEl)
198
245
  })
199
246
  });
247
+
248
+ // Inicialización del modal de onboarding
249
+ document.addEventListener('DOMContentLoaded', function () {
250
+ const btn = document.getElementById('onboarding-button');
251
+ if (!btn) return;
252
+
253
+ const modalEl = document.getElementById('onboardingModal');
254
+ const modal = new bootstrap.Modal(modalEl);
255
+
256
+ if (!window.initOnboarding) {
257
+ console.error('initOnboarding no disponible. Verifica chat_onboarding.js');
258
+ return;
259
+ }
260
+
261
+ const onboarding = initOnboarding({
262
+ mode: 'modal',
263
+ cards: Array.isArray(window.onboardingCards) ? window.onboardingCards : [],
264
+ ui: {
265
+ icon: '#ob-icon',
266
+ title: '#ob-title',
267
+ text: '#ob-text',
268
+ dots: '#ob-dots',
269
+ prev: '#ob-prev',
270
+ next: '#ob-next'
271
+ },
272
+ autoRotateMs: 5000
273
+ });
274
+
275
+ modalEl.addEventListener('hidden.bs.modal', () => onboarding.stop());
276
+
277
+ btn.addEventListener('click', () => {
278
+ if (!onboarding.hasCards()) {
279
+ toastr.info('No hay información de onboarding disponible.');
280
+ return;
281
+ }
282
+ onboarding.start();
283
+ modal.show();
284
+ });
285
+ });
200
286
  </script>
201
287
  {% endblock %}