iatoolkit 0.11.0__py3-none-any.whl → 0.71.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 (122) hide show
  1. iatoolkit/__init__.py +2 -6
  2. iatoolkit/base_company.py +9 -29
  3. iatoolkit/cli_commands.py +1 -1
  4. iatoolkit/common/routes.py +96 -52
  5. iatoolkit/common/session_manager.py +2 -1
  6. iatoolkit/common/util.py +17 -27
  7. iatoolkit/company_registry.py +1 -2
  8. iatoolkit/iatoolkit.py +97 -53
  9. iatoolkit/infra/llm_client.py +15 -20
  10. iatoolkit/infra/llm_proxy.py +38 -10
  11. iatoolkit/infra/openai_adapter.py +1 -1
  12. iatoolkit/infra/redis_session_manager.py +48 -2
  13. iatoolkit/locales/en.yaml +167 -0
  14. iatoolkit/locales/es.yaml +163 -0
  15. iatoolkit/repositories/database_manager.py +23 -3
  16. iatoolkit/repositories/document_repo.py +1 -1
  17. iatoolkit/repositories/models.py +35 -10
  18. iatoolkit/repositories/profile_repo.py +3 -2
  19. iatoolkit/repositories/vs_repo.py +26 -20
  20. iatoolkit/services/auth_service.py +193 -0
  21. iatoolkit/services/branding_service.py +70 -25
  22. iatoolkit/services/company_context_service.py +155 -0
  23. iatoolkit/services/configuration_service.py +133 -0
  24. iatoolkit/services/dispatcher_service.py +80 -105
  25. iatoolkit/services/document_service.py +5 -2
  26. iatoolkit/services/embedding_service.py +146 -0
  27. iatoolkit/services/excel_service.py +30 -26
  28. iatoolkit/services/file_processor_service.py +4 -12
  29. iatoolkit/services/history_service.py +7 -16
  30. iatoolkit/services/i18n_service.py +104 -0
  31. iatoolkit/services/jwt_service.py +18 -29
  32. iatoolkit/services/language_service.py +83 -0
  33. iatoolkit/services/load_documents_service.py +100 -113
  34. iatoolkit/services/mail_service.py +9 -4
  35. iatoolkit/services/profile_service.py +152 -76
  36. iatoolkit/services/prompt_manager_service.py +20 -16
  37. iatoolkit/services/query_service.py +208 -96
  38. iatoolkit/services/search_service.py +11 -4
  39. iatoolkit/services/sql_service.py +57 -25
  40. iatoolkit/services/tasks_service.py +1 -1
  41. iatoolkit/services/user_feedback_service.py +72 -34
  42. iatoolkit/services/user_session_context_service.py +112 -54
  43. iatoolkit/static/images/fernando.jpeg +0 -0
  44. iatoolkit/static/js/chat_feedback_button.js +80 -0
  45. iatoolkit/static/js/chat_help_content.js +124 -0
  46. iatoolkit/static/js/chat_history_button.js +110 -0
  47. iatoolkit/static/js/chat_logout_button.js +36 -0
  48. iatoolkit/static/js/chat_main.js +135 -222
  49. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  50. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  51. iatoolkit/static/js/chat_reload_button.js +35 -0
  52. iatoolkit/static/styles/chat_iatoolkit.css +289 -210
  53. iatoolkit/static/styles/chat_modal.css +63 -77
  54. iatoolkit/static/styles/chat_public.css +107 -0
  55. iatoolkit/static/styles/landing_page.css +182 -0
  56. iatoolkit/static/styles/onboarding.css +176 -0
  57. iatoolkit/system_prompts/query_main.prompt +5 -22
  58. iatoolkit/templates/_company_header.html +20 -0
  59. iatoolkit/templates/_login_widget.html +42 -0
  60. iatoolkit/templates/base.html +40 -20
  61. iatoolkit/templates/change_password.html +57 -36
  62. iatoolkit/templates/chat.html +180 -86
  63. iatoolkit/templates/chat_modals.html +138 -68
  64. iatoolkit/templates/error.html +44 -8
  65. iatoolkit/templates/forgot_password.html +40 -23
  66. iatoolkit/templates/index.html +145 -0
  67. iatoolkit/templates/login_simulation.html +45 -0
  68. iatoolkit/templates/onboarding_shell.html +107 -0
  69. iatoolkit/templates/signup.html +63 -65
  70. iatoolkit/views/base_login_view.py +91 -0
  71. iatoolkit/views/change_password_view.py +56 -31
  72. iatoolkit/views/embedding_api_view.py +65 -0
  73. iatoolkit/views/external_login_view.py +61 -28
  74. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +10 -3
  75. iatoolkit/views/forgot_password_view.py +27 -21
  76. iatoolkit/views/help_content_api_view.py +54 -0
  77. iatoolkit/views/history_api_view.py +56 -0
  78. iatoolkit/views/home_view.py +50 -23
  79. iatoolkit/views/index_view.py +14 -0
  80. iatoolkit/views/init_context_api_view.py +74 -0
  81. iatoolkit/views/llmquery_api_view.py +58 -0
  82. iatoolkit/views/login_simulation_view.py +93 -0
  83. iatoolkit/views/login_view.py +130 -37
  84. iatoolkit/views/logout_api_view.py +49 -0
  85. iatoolkit/views/profile_api_view.py +46 -0
  86. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
  87. iatoolkit/views/signup_view.py +41 -36
  88. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  89. iatoolkit/views/tasks_review_api_view.py +55 -0
  90. iatoolkit/views/user_feedback_api_view.py +60 -0
  91. iatoolkit/views/verify_user_view.py +34 -29
  92. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +41 -23
  93. iatoolkit-0.71.2.dist-info/RECORD +122 -0
  94. iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
  95. iatoolkit/common/auth.py +0 -200
  96. iatoolkit/static/images/arrow_up.png +0 -0
  97. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  98. iatoolkit/static/images/logo_clinica.png +0 -0
  99. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  100. iatoolkit/static/images/logo_maxxa.png +0 -0
  101. iatoolkit/static/images/logo_notaria.png +0 -0
  102. iatoolkit/static/images/logo_tarjeta.png +0 -0
  103. iatoolkit/static/images/logo_umayor.png +0 -0
  104. iatoolkit/static/images/upload.png +0 -0
  105. iatoolkit/static/js/chat_feedback.js +0 -115
  106. iatoolkit/static/js/chat_history.js +0 -117
  107. iatoolkit/static/styles/chat_info.css +0 -53
  108. iatoolkit/templates/header.html +0 -31
  109. iatoolkit/templates/home.html +0 -199
  110. iatoolkit/templates/login.html +0 -43
  111. iatoolkit/templates/test.html +0 -9
  112. iatoolkit/views/chat_token_request_view.py +0 -98
  113. iatoolkit/views/chat_view.py +0 -58
  114. iatoolkit/views/download_file_view.py +0 -58
  115. iatoolkit/views/external_chat_login_view.py +0 -95
  116. iatoolkit/views/history_view.py +0 -57
  117. iatoolkit/views/llmquery_view.py +0 -65
  118. iatoolkit/views/tasks_review_view.py +0 -83
  119. iatoolkit/views/user_feedback_view.py +0 -74
  120. iatoolkit-0.11.0.dist-info/RECORD +0 -110
  121. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
  122. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
@@ -1,115 +0,0 @@
1
- $(document).ready(function () {
2
-
3
- // Evento para enviar el feedback
4
- $('#submit-feedback').on('click', async function() {
5
- const feedbackText = $('#feedback-text').val().trim();
6
- const submitButton = $(this);
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>&times;</span></button>'; // Versión BS3/BS4
16
- // --- FIN DE LA LÓGICA DE COMPATIBILIDAD ---
17
-
18
- if (!feedbackText) {
19
- const alertHtml = `
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);
26
- return;
27
- }
28
-
29
- const activeStars = $('.star.active').length;
30
- if (activeStars === 0) {
31
- const alertHtml = `
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);
38
- return;
39
- }
40
-
41
- submitButton.prop('disabled', true);
42
- submitButton.html('<i class="bi bi-send me-1 icon-spaced"></i>Enviando...');
43
-
44
- const response = await sendFeedback(feedbackText);
45
-
46
- $('#feedbackModal').modal('hide');
47
-
48
- if (response) {
49
- Swal.fire({ icon: 'success', title: 'Feedback enviado', text: 'Gracias por tu comentario.' });
50
- } else {
51
- Swal.fire({ icon: 'error', title: 'Error', text: 'No se pudo enviar el feedback, intente nuevamente.' });
52
- }
53
- });
54
-
55
- // Evento para abrir el modal de feedback
56
- $('#send-feedback-button').on('click', function() {
57
- $('#submit-feedback').prop('disabled', false);
58
- $('#submit-feedback').html('<i class="bi bi-send me-1 icon-spaced"></i>Enviar');
59
- $('.star').removeClass('active hover-active'); // Resetea estrellas
60
- $('#feedback-text').val(''); // Limpia texto
61
- $('.modal-body .alert').remove(); // Quita alertas previas
62
- $('#feedbackModal').modal('show');
63
- });
64
-
65
- // Evento que se dispara DESPUÉS de que el modal se ha ocultado
66
- $('#feedbackModal').on('hidden.bs.modal', function () {
67
- $('#feedback-text').val('');
68
- $('.modal-body .alert').remove();
69
- $('.star').removeClass('active');
70
- });
71
-
72
- // Función para el sistema de estrellas
73
- window.gfg = function(rating) {
74
- $('.star').removeClass('active');
75
- $('.star').each(function(index) {
76
- if (index < rating) {
77
- $(this).addClass('active');
78
- }
79
- });
80
- };
81
-
82
- $('.star').hover(
83
- function() {
84
- const rating = $(this).data('rating');
85
- $('.star').removeClass('hover-active');
86
- $('.star').each(function(index) {
87
- if ($(this).data('rating') <= rating) {
88
- $(this).addClass('hover-active');
89
- }
90
- });
91
- },
92
- function() {
93
- $('.star').removeClass('hover-active');
94
- }
95
- );
96
- });
97
-
98
- const sendFeedback = async function(message) {
99
- const activeStars = $('.star.active').length;
100
- const data = {
101
- "external_user_id": window.externalUserId,
102
- "message": message,
103
- "rating": activeStars,
104
- "space": "spaces/AAQAupQldd4", // Este valor podría necesitar ser dinámico
105
- "type": "MESSAGE_TRIGGER"
106
- };
107
- try {
108
- // Asumiendo que callLLMAPI está definido globalmente en otro archivo (ej. chat_main.js)
109
- const responseData = await callLLMAPI('/feedback', data, "POST");
110
- return responseData;
111
- } catch (error) {
112
- console.error("Error al enviar feedback:", error);
113
- return null;
114
- }
115
- }
@@ -1,117 +0,0 @@
1
- $(document).ready(function () {
2
- // Evento para abrir el modal de historial
3
- $('#history-button').on('click', function() {
4
- loadHistory();
5
- $('#historyModal').modal('show');
6
- });
7
-
8
- // Variables globales para el historial
9
- let historyData = [];
10
-
11
- // Función para cargar el historial
12
- async function loadHistory() {
13
- const historyLoading = $('#history-loading');
14
- const historyError = $('#history-error');
15
- const historyContent = $('#history-content');
16
-
17
- // Mostrar loading
18
- historyLoading.show();
19
- historyError.hide();
20
- historyContent.hide();
21
-
22
- try {
23
- const data = {
24
- external_user_id: window.externalUserId
25
- };
26
-
27
- const responseData = await callLLMAPI("/history", data, "POST");
28
-
29
- if (responseData && responseData.history) {
30
- // Guardar datos globalmente
31
- historyData = responseData.history;
32
-
33
- // Mostrar todos los datos
34
- displayAllHistory();
35
-
36
- // Mostrar contenido
37
- historyContent.show();
38
- } else {
39
- throw new Error('No se recibieron datos del historial');
40
- }
41
- } catch (error) {
42
- console.error("Error al cargar historial:", error);
43
- const errorHtml = `
44
- <div class="alert alert-branded-danger alert-dismissible show" role="alert">
45
- <strong>Error al cargar el historial:</strong> ${error.message}
46
- <button type="button" class="close" data-dismiss="alert">
47
- <span>&times;</span>
48
- </button>
49
- </div>
50
- `;
51
- historyError.html(errorHtml).show();
52
- } finally {
53
- historyLoading.hide();
54
- }
55
- }
56
-
57
- // Función para mostrar todo el historial
58
- function displayAllHistory() {
59
- const historyTableBody = $('#history-table-body');
60
-
61
- // Limpiar tabla
62
- historyTableBody.empty();
63
-
64
- // Filtrar solo consultas que son strings simples (no objetos JSON)
65
- const filteredHistory = historyData.filter(item => {
66
- try {
67
- // Intentar parsear como JSON
68
- const parsed = JSON.parse(item.query);
69
- // Si se puede parsear y es un objeto, filtrarlo
70
- return false;
71
- } catch (e) {
72
- // Si no se puede parsear, es un string simple, incluirlo
73
- return true;
74
- }
75
- });
76
-
77
- // Poblar tabla solo con las consultas filtradas
78
- filteredHistory.forEach((item, index) => {
79
- const row = $(`
80
- <tr>
81
- <td>${index + 1}</td>
82
- <td>${formatDate(item.created_at)}</td>
83
- <td class="query-cell" style="cursor: pointer;" title="Haz clic para copiar esta consulta al chat">${item.query}</td>
84
- </tr>
85
- `);
86
- historyTableBody.append(row);
87
- });
88
-
89
- // Agregar evento de clic a las celdas de consulta
90
- historyTableBody.on('click', '.query-cell', function() {
91
- const queryText = $(this).text();
92
-
93
- // Copiar el texto al textarea del chat
94
- $('#question').val(queryText);
95
-
96
- // Cerrar el modal
97
- $('#historyModal').modal('hide');
98
-
99
- // Hacer focus en el textarea para que el usuario pueda editar si lo desea
100
- $('#question').focus();
101
- });
102
- }
103
-
104
- // Función para formatear fecha
105
- function formatDate(dateString) {
106
- const date = new Date(dateString);
107
- return date.toLocaleDateString('es-CL', {
108
- day: '2-digit',
109
- month: '2-digit',
110
- year: 'numeric',
111
- hour: '2-digit',
112
- minute: '2-digit'
113
- });
114
- }
115
-
116
- });
117
-
@@ -1,53 +0,0 @@
1
- /*
2
- * Estilo para el encabezado de la tabla (thead).
3
- * Se usa un fondo gris claro, texto en mayúsculas y
4
- * un espaciado de letras para un look más profesional.
5
- */
6
-
7
- .table{
8
- background-color: white;
9
- }
10
-
11
- .title-info-page{
12
- color: #495057;
13
- font-size: 24px;
14
- font-weight: bold;
15
- margin-bottom: 10px;
16
- }
17
-
18
- .table thead th {
19
- background-color: #f8f9fa;
20
- color: #495057;
21
- text-transform: uppercase;
22
- letter-spacing: 0.5px;
23
- border-bottom-width: 2px;
24
- }
25
-
26
- /*
27
- * Columna 1: Ancho mínimo, sin salto de línea y en negrita.
28
- */
29
- .table th:nth-child(1),
30
- .table td:nth-child(1) {
31
- width: 1%;
32
- white-space: nowrap;
33
- font-weight: bold;
34
- }
35
-
36
- /*
37
- * Columna 2: Ancho mínimo y sin salto de línea.
38
- */
39
- .table th:nth-child(2),
40
- .table td:nth-child(2) {
41
- width: 1%;
42
- white-space: nowrap;
43
- }
44
-
45
- /*
46
- * Columna 3 (Descripción): Limita su ancho máximo
47
- * y ajusta el texto largo para que no se desborde.
48
- */
49
- .table th:nth-child(3),
50
- .table td:nth-child(3) {
51
- max-width: 450px;
52
- word-wrap: break-word;
53
- }
@@ -1,31 +0,0 @@
1
- {% if not external_login %}
2
- <header class="modern-header">
3
- {% if not is_mobile or not user %}
4
- <div class="logo-section">
5
- <a href="{{ url_for('chat', company_short_name=company_short_name) }}" title="Ir a Inicio">
6
- {% if company_short_name %}
7
- <img src="{{ url_for('static', filename='images/' + company.logo_file) }}" alt="Logo de la empresa">
8
- {% else %}
9
- <img src="{{ url_for('static', filename='images/logo_iatoolkit.png') }}" alt="Logo predeterminado">
10
- {% endif %}
11
- </a>
12
- </div>
13
- {% endif %}
14
- {% if user and user_company == company_short_name %}
15
- <div class="d-flex ms-auto align-items-center">
16
- <!-- Nombres del usuario y empresa -->
17
- <div class="d-flex flex-column {% if not is_mobile %} align-items-end {% endif %} me-3">
18
- <span class="text-white fw-semibold">
19
- {{ user.email }}
20
- </span>
21
- </div>
22
-
23
- <!-- Icono de cerrar sesión -->
24
- <a href="{{ url_for('logout', company_short_name=company_short_name) }}"
25
- | class="text-white fs-4 ms-3" title="Cerrar sesión">
26
- <i class="bi bi-box-arrow-right"></i>
27
- </a>
28
- </div>
29
- {% endif %}
30
- </header>
31
- {% endif %}
@@ -1,199 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Inicio - IAToolkit{% endblock %}
4
-
5
- {% block content %}
6
-
7
- <!-- Contenido principal con espaciado adicional respecto al header -->
8
- <div class="row flex-fill mt-5 flex-wrap">
9
-
10
- {% if not user or user_company != company_short_name %}
11
- <!-- Sección de login (se coloca primero en el HTML para mobile) -->
12
- <div class="col-12 col-lg-5 offset-lg-1">
13
- <div class="border rounded p-4 shadow-sm bg-light">
14
- <h4 class="text-muted fw-semibold text-start mb-3">login integrado (IAToolkit)</h4>
15
- <form id="login-form"
16
- action="{{ url_for('home', company_short_name=company_short_name) }}"
17
- method="post">
18
- <div class="mb-3">
19
- <label for="company_short_name" class="form-label d-block text-muted">Empresa</label>
20
- <select id="company_short_name" name="company_short_name" class="form-select" required>
21
- <option value="" disabled selected>Selecciona una empresa</option>
22
- {% for company in companies %}
23
- <option value="{{ company.short_name }}"
24
- {% if company.short_name == company_short_name %}selected{% endif %}>
25
- {{ company.short_name }}
26
- </option>
27
- {% endfor %}
28
- </select>
29
- </div>
30
-
31
- <div class="mb-3">
32
- <label for="email" class="form-label d-block text-muted">Correo Electrónico</label>
33
- <input type="email" id="email" name="email" class="form-control" required>
34
- </div>
35
- <div class="mb-3">
36
- <label for="password" class="form-label d-block text-muted">Contraseña</label>
37
- <input type="password" id="password" name="password" class="form-control" required>
38
- </div>
39
- <button type="submit" class="btn btn-primary w-100">
40
- Iniciar Sesión</button>
41
- </form>
42
-
43
- <div class="text-center mt-3">
44
- <a href="{% if company_short_name %}{{ url_for('signup', company_short_name=company_short_name) }}{% else %}#{% endif %}"
45
- id="signup-link"
46
- class="btn btn-outline-primary w-100">Registrarse</a>
47
- </div>
48
- <div class="text-center mt-3">
49
- <a href="{{ url_for('forgot_password', company_short_name=company_short_name) }}" class="text-decoration-none text-muted fw-semibold">
50
- ¿Olvidaste tu contraseña?
51
- </a>
52
- </div>
53
- </div>
54
- </div>
55
- {% endif %}
56
-
57
- <!-- Sección de JWT -->
58
- <div class="col-12 col-lg-5 offset-lg-1">
59
- <div class="border rounded p-4 shadow-sm bg-light">
60
- <h4 class="text-muted fw-semibold text-start mb-3">login externo (api-key)</h4>
61
- <form id="jwt-form" method="post">
62
- <div class="mb-3">
63
- <label for="company_name" class="form-label d-block text-muted">Empresa</label>
64
- <select id="company_name" name="company_short_name" class="form-select" required>
65
- <option value="" disabled selected>Selecciona una empresa</option>
66
- {% for company in companies %}
67
- {% if company.allow_jwt %}
68
- <option value="{{ company.short_name }}"> {{ company.short_name }}
69
- </option>
70
- {% endif %}
71
- {% endfor %}
72
- </select>
73
- </div>
74
-
75
- <div class="mb-3">
76
- <label for="external_user_id" class="form-label d-block text-muted">External user ID</label>
77
- <input type="text" id="external_user_id" name="external_user_id" class="form-control" required>
78
- </div>
79
-
80
- <button type="button"
81
- id="initiateJwtChatButton"
82
- class="ml-5 btn btn-primary">
83
- <span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
84
- Iniciar Sesión JWT
85
- </button>
86
- </form>
87
- </div>
88
- </div>
89
-
90
- </div>
91
-
92
- {% endblock %}
93
-
94
- {% block scripts %}
95
-
96
- <script>
97
- // Variables pasadas desde Flask (HomeView)
98
- const API_KEY = "{{ api_key|safe }}";
99
-
100
- $(document).ready(function () {
101
- // Función para actualizar el enlace de "Registrarse" y el action del formulario "Iniciar Sesión"
102
- function updateLinksAndForm() {
103
- const selectedCompany = $('#company_short_name').val(); // Obtenemos el valor del select
104
-
105
- // Actualizar enlace "Registrarse"
106
- if (selectedCompany && selectedCompany.trim() !== '') {
107
- const signupUrl = '/' + selectedCompany + '/signup';
108
- $('#signup-link').attr('href', signupUrl); // Actualizamos el href del botón "Registrarse"
109
- } else {
110
- $('#signup-link').attr('href', '#'); // Enlace a "#" si no hay empresa seleccionada
111
- }
112
-
113
- // Actualizar action del formulario "Iniciar Sesión"
114
- if (selectedCompany && selectedCompany.trim() !== '') {
115
- const loginAction = '/' + selectedCompany + '/login';
116
- $('#login-form').attr('action', loginAction); // Actualizamos la URL del form
117
- } else {
118
- $('#login-form').attr('action', '#'); // URL genérica si no hay selección
119
- }
120
- }
121
-
122
- // Actualizamos al cargar la página
123
- updateLinksAndForm();
124
-
125
- // Escuchamos el evento de cambio en el dropdown para actualizar dinámicamente
126
- $('#company_short_name').on('change', function () {
127
- updateLinksAndForm();
128
- });
129
-
130
- // Interceptamos el click en "Registrarse"
131
- $('#signup-link').on('click', function (e) {
132
- const selectedCompany = $('#company_short_name').val();
133
-
134
- if (!selectedCompany || selectedCompany.trim() === '') {
135
- e.preventDefault(); // evitar navegación al #
136
- Swal.fire({
137
- icon: 'warning',
138
- title: 'Empresa no seleccionada',
139
- text: 'Por favor, selecciona una empresa antes de registrarte.'
140
- });
141
- }
142
- });
143
-
144
- // Event listener para el botón de "Abrir Chat (JWT)"
145
- $('#initiateJwtChatButton').on('click', function() {
146
-
147
- const selectedCompany = $('#company_name').val();
148
- const externalUserId = $('#external_user_id').val();
149
-
150
- if (!selectedCompany || !externalUserId.trim()) {
151
- Swal.fire({ icon: 'warning', title: 'Campos Requeridos', text: 'Por favor, selecciona una empresa e ingresa un ID de usuario.' });
152
- return;
153
- }
154
- if (!API_KEY || API_KEY.includes("defecto")) {
155
- Swal.fire({ icon: 'error', title: 'Error de Configuración', text: 'La API Key de la aplicación no está disponible.' });
156
- return;
157
- }
158
-
159
- const $button = $(this);
160
- const $spinner = $button.find('.spinner-border');
161
- $button.prop('disabled', true);
162
- $spinner.removeClass('d-none');
163
-
164
- fetch(`/${selectedCompany}/chat_login`, {
165
- method: 'POST',
166
- headers: {
167
- 'Content-Type': 'application/json',
168
- 'Authorization': `Bearer ${API_KEY}`
169
- },
170
- body: JSON.stringify({
171
- external_user_id: externalUserId
172
- })
173
- })
174
- .then(async response => {
175
- if (response.ok) { // Si el status es 200, la respuesta es el HTML
176
- return response.text();
177
- } else { // Si hay un error, el cuerpo es JSON
178
- const errorData = await response.json();
179
- throw new Error(errorData.error || 'Ocurrió un error desconocido.');
180
- }
181
- })
182
- .then(htmlContent => {
183
- // Éxito: Abrimos el HTML que nos devolvió el servidor en una nueva pestaña.
184
- const newTab = window.open();
185
- newTab.document.write(htmlContent);
186
- newTab.document.close();
187
- })
188
- .catch(error => {
189
- Swal.fire({ icon: 'error', title: 'Error de Inicio de Sesión', text: error.message });
190
- })
191
- .finally(() => {
192
- $button.prop('disabled', false);
193
- $spinner.addClass('d-none');
194
- });
195
-
196
- });
197
- });
198
- </script>
199
- {% endblock %}
@@ -1,43 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Inicio de Sesión{% endblock %}
4
-
5
- {% block content %}
6
- <div class="container d-flex justify-content-center align-items-center vh-100">
7
- <div class="col-11 col-md-6 col-lg-4 border rounded p-3 shadow-sm">
8
- <h4 class="text-muted fw-semibold text-start mb-3">Iniciar Sesión - {{ company.name }}
9
- </h4>
10
- <form action="{{ url_for('login', company_short_name=company_short_name) }}"
11
- method="post">
12
- <div class="mb-3">
13
- <label for="email" class="form-label text-muted">Correo Electrónico</label>
14
- <input type="email" id="email" name="email"
15
- class="form-control" required
16
- value="{{ form_data.email if form_data else '' }}">
17
- </div>
18
- <div class="mb-3">
19
- <label for="password" class="form-label">Contraseña</label>
20
- <input type="password" id="password" name="password"
21
- class="form-control" required
22
- value="{{ form_data.password if form_data else '' }}">
23
- </div>
24
- <button type="submit" class="btn btn-primary w-100">Iniciar Sesión</button>
25
-
26
- <p class="text-muted text-start mt-3" style="text-align: justify;">
27
- Ingresa tus credenciales para acceder a tu cuenta. Si olvidaste tu contraseña,
28
- puedes recuperarla fácilmente a través del siguiente enlace.
29
- </p>
30
- <div class="text-center mt-3">
31
- <a href="{{ url_for('signup', company_short_name=company_short_name) }}">
32
- ¿No tienes cuenta? Regístrate
33
- </a>
34
- </div>
35
- <div class="text-center mt-3">
36
- <a href="{{ url_for('forgot_password', company_short_name=company_short_name) }}">
37
- ¿Olvidaste tu contraseña?
38
- </a>
39
- </div>
40
- </form>
41
- </div>
42
- </div>
43
- {% endblock %}
@@ -1,9 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Prueba</title>
5
- </head>
6
- <body>
7
- <h1>Prueba Básica con Flask-Injector</h1>
8
- </body>
9
- </html>
@@ -1,98 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- from flask import request, jsonify, current_app
7
- from flask.views import MethodView
8
- from injector import inject
9
- import logging
10
- from iatoolkit.repositories.profile_repo import ProfileRepo
11
- from iatoolkit.services.jwt_service import JWTService
12
- from typing import Optional
13
-
14
-
15
- # Necesitaremos JWT_EXPIRATION_SECONDS_CHAT de la configuración de la app
16
- # Se podría inyectar o acceder globalmente.
17
-
18
- class ChatTokenRequestView(MethodView):
19
- @inject
20
- def __init__(self, profile_repo: ProfileRepo, jwt_service: JWTService):
21
- self.profile_repo = profile_repo
22
- self.jwt_service = jwt_service
23
-
24
- def _authenticate_requesting_company_via_api_key(self) -> tuple[
25
- Optional[int], Optional[str], Optional[tuple[dict, int]]]:
26
- """
27
- Autentica a la compañía que solicita el token JWT usando su API Key.
28
- Retorna (company_id, company_short_name, None) en éxito.
29
- Retorna (None, None, (error_json, status_code)) en fallo.
30
- """
31
- api_key_header = request.headers.get('Authorization')
32
- if not api_key_header or not api_key_header.startswith('Bearer '):
33
- return None, None, ({"error": "API Key faltante o mal formada en el header Authorization"}, 401)
34
-
35
- api_key_value = api_key_header.split('Bearer ')[1]
36
- try:
37
- api_key_entry = self.profile_repo.get_active_api_key_entry(api_key_value)
38
- if not api_key_entry:
39
- return None, None, ({"error": "API Key inválida o inactiva"}, 401)
40
-
41
- # api_key_entry.company ya está cargado por joinedload en get_active_api_key_entry
42
- if not api_key_entry.company: # Sanity check
43
- logging.error(
44
- f"ChatTokenRequest: API Key {api_key_value[:5]}... no tiene compañía asociada a pesar de ser válida.")
45
- return None, None, ({"error": "Error interno del servidor al verificar API Key"}, 500)
46
-
47
- return api_key_entry.company_id, api_key_entry.company.short_name, None
48
-
49
- except Exception as e:
50
- logging.exception(f"ChatTokenRequest: Error interno durante validación de API Key: {e}")
51
- return None, None, ({"error": "Error interno del servidor al validar API Key"}, 500)
52
-
53
- def post(self):
54
- """
55
- Genera un JWT para una sesión de chat.
56
- Autenticado por API Key de la empresa.
57
- Requiere JSON body:
58
- {"company_short_name": "target_company_name",
59
- "external_user_id": "user_abc"
60
- }
61
- """
62
- # only requests with valid api-key are allowed
63
- auth_company_id, auth_company_short_name, error = self._authenticate_requesting_company_via_api_key()
64
- if error:
65
- return jsonify(error[0]), error[1]
66
-
67
- # get the json fields from the request body
68
- data = request.get_json()
69
- if not data:
70
- return jsonify({"error": "Cuerpo de la solicitud JSON faltante"}), 400
71
-
72
- target_company_short_name = data.get('company_short_name')
73
- external_user_id = data.get('external_user_id')
74
-
75
- if not target_company_short_name or not external_user_id:
76
- return jsonify(
77
- {"error": "Faltan 'company_short_name' o 'external_user_id' en el cuerpo de la solicitud"}), 401
78
-
79
- # Verificar que la API Key usada pertenezca a la empresa para la cual se solicita el token
80
- if auth_company_short_name != target_company_short_name:
81
- return jsonify({
82
- "error": f"API Key no autorizada para generar tokens para la compañía '{target_company_short_name}'"}), 403
83
-
84
- jwt_expiration_seconds = current_app.config.get('JWT_EXPIRATION_SECONDS_CHAT', 3600)
85
-
86
- # Aquí, auth_company_id es el ID de la compañía para la que se generará el token.
87
- # auth_company_short_name es su nombre corto.
88
- token = self.jwt_service.generate_chat_jwt(
89
- company_id=auth_company_id,
90
- company_short_name=auth_company_short_name, # Usamos el short_name autenticado
91
- external_user_id=external_user_id,
92
- expires_delta_seconds=jwt_expiration_seconds
93
- )
94
-
95
- if token:
96
- return jsonify({"chat_jwt": token}), 200
97
- else:
98
- return jsonify({"error": "No se pudo generar el token de chat"}), 500