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
@@ -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
@@ -1,58 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- from flask import render_template, request, jsonify
7
- from iatoolkit.services.profile_service import ProfileService
8
- from flask.views import MethodView
9
- from injector import inject
10
- import os
11
- from iatoolkit.common.auth import IAuthentication
12
- from iatoolkit.services.prompt_manager_service import PromptService
13
- from iatoolkit.services.branding_service import BrandingService
14
-
15
-
16
- class ChatView(MethodView):
17
- @inject
18
- def __init__(self,
19
- iauthentication: IAuthentication,
20
- prompt_service: PromptService,
21
- profile_service: ProfileService,
22
- branding_service: BrandingService
23
- ):
24
- self.iauthentication = iauthentication
25
- self.profile_service = profile_service
26
- self.prompt_service = prompt_service
27
- self.branding_service = branding_service
28
-
29
- def get(self, company_short_name: str):
30
- # get access credentials
31
- iaut = self.iauthentication.verify(company_short_name)
32
- if not iaut.get("success"):
33
- return jsonify(iaut), 401
34
-
35
- user_agent = request.user_agent
36
- is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
37
- alert_message = request.args.get('alert_message', None)
38
-
39
- # 1. get company info
40
- company = self.profile_service.get_company_by_short_name(company_short_name)
41
- if not company:
42
- return render_template('error.html', message="Empresa no encontrada"), 404
43
-
44
- # 2. get the company prompts
45
- prompts = self.prompt_service.get_user_prompts(company_short_name)
46
-
47
- # 3. get the branding data
48
- branding_data = self.branding_service.get_company_branding(company)
49
-
50
- return render_template("chat.html",
51
- branding=branding_data,
52
- company_short_name=company_short_name,
53
- is_mobile=is_mobile,
54
- alert_message=alert_message,
55
- alert_icon='success' if alert_message else None,
56
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL', 'http://localhost:5000'),
57
- prompts=prompts
58
- )
@@ -1,58 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import logging
7
- import os
8
-
9
- from flask import current_app, jsonify, send_from_directory
10
- from flask.views import MethodView
11
- from injector import inject
12
-
13
- from iatoolkit.common.auth import IAuthentication
14
- from iatoolkit.services.excel_service import ExcelService
15
- from iatoolkit.services.profile_service import ProfileService
16
-
17
-
18
- class DownloadFileView(MethodView):
19
- @inject
20
- def __init__(self, iauthentication: IAuthentication, profile_service: ProfileService, excel_service: ExcelService):
21
- self.iauthentication = iauthentication
22
- self.profile_service = profile_service
23
- self.excel_service = excel_service
24
-
25
- def get(self, company_short_name: str, external_user_id: str, filename: str):
26
- if not external_user_id:
27
- return jsonify({"error": "Falta external_user_id"}), 400
28
-
29
- iauth = self.iauthentication.verify(
30
- company_short_name,
31
- body_external_user_id=external_user_id
32
- )
33
- if not iauth.get("success"):
34
- return jsonify(iauth), 401
35
-
36
- company = self.profile_service.get_company_by_short_name(company_short_name)
37
- if not company:
38
- return jsonify({"error": "Empresa no encontrada"}), 404
39
-
40
- file_validation = self.excel_service.validate_file_access(filename)
41
- if file_validation:
42
- return file_validation
43
-
44
- temp_dir = os.path.join(current_app.root_path, 'static', 'temp')
45
-
46
- try:
47
- response = send_from_directory(
48
- temp_dir,
49
- filename,
50
- as_attachment=True,
51
- mimetype='application/octet-stream'
52
- )
53
- logging.info(f"Archivo descargado via API: {filename}")
54
- return response
55
- except Exception as e:
56
- logging.error(f"Error descargando archivo {filename}: {str(e)}")
57
- return jsonify({"error": "Error descargando archivo"}), 500
58
-