iatoolkit 0.59.1__py3-none-any.whl → 0.67.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 +2 -0
- iatoolkit/base_company.py +2 -19
- iatoolkit/common/routes.py +28 -25
- iatoolkit/common/session_manager.py +2 -0
- iatoolkit/common/util.py +17 -6
- iatoolkit/company_registry.py +1 -2
- iatoolkit/iatoolkit.py +54 -20
- iatoolkit/locales/en.yaml +167 -0
- iatoolkit/locales/es.yaml +163 -0
- iatoolkit/repositories/database_manager.py +3 -3
- iatoolkit/repositories/document_repo.py +1 -1
- iatoolkit/repositories/models.py +3 -4
- iatoolkit/repositories/profile_repo.py +0 -4
- iatoolkit/services/auth_service.py +44 -32
- iatoolkit/services/branding_service.py +35 -27
- iatoolkit/services/configuration_service.py +140 -0
- iatoolkit/services/dispatcher_service.py +20 -18
- 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 +79 -0
- iatoolkit/services/load_documents_service.py +4 -4
- iatoolkit/services/mail_service.py +9 -4
- iatoolkit/services/onboarding_service.py +10 -4
- iatoolkit/services/profile_service.py +59 -38
- iatoolkit/services/prompt_manager_service.py +20 -16
- iatoolkit/services/query_service.py +15 -14
- iatoolkit/services/sql_service.py +6 -2
- iatoolkit/services/user_feedback_service.py +70 -29
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +110 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +32 -184
- iatoolkit/static/js/{chat_onboarding.js → chat_onboarding_button.js} +0 -1
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +251 -205
- iatoolkit/static/styles/chat_modal.css +63 -95
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +121 -167
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +10 -10
- iatoolkit/templates/base.html +36 -19
- iatoolkit/templates/change_password.html +24 -22
- iatoolkit/templates/chat.html +121 -93
- iatoolkit/templates/chat_modals.html +113 -74
- iatoolkit/templates/error.html +44 -8
- iatoolkit/templates/forgot_password.html +17 -15
- iatoolkit/templates/index.html +66 -81
- iatoolkit/templates/login_simulation.html +16 -5
- iatoolkit/templates/onboarding_shell.html +1 -2
- iatoolkit/templates/signup.html +22 -20
- iatoolkit/views/base_login_view.py +12 -1
- iatoolkit/views/change_password_view.py +50 -33
- iatoolkit/views/external_login_view.py +5 -11
- iatoolkit/views/file_store_api_view.py +7 -9
- iatoolkit/views/forgot_password_view.py +21 -19
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +16 -12
- iatoolkit/views/home_view.py +61 -0
- iatoolkit/views/index_view.py +5 -34
- iatoolkit/views/init_context_api_view.py +16 -13
- iatoolkit/views/llmquery_api_view.py +38 -28
- iatoolkit/views/login_simulation_view.py +14 -2
- iatoolkit/views/login_view.py +48 -33
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +8 -8
- iatoolkit/views/signup_view.py +27 -25
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +21 -32
- iatoolkit/views/verify_user_view.py +33 -26
- {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/METADATA +40 -22
- iatoolkit-0.67.0.dist-info/RECORD +120 -0
- iatoolkit-0.67.0.dist-info/licenses/LICENSE +21 -0
- iatoolkit/static/js/chat_context_reload.js +0 -54
- iatoolkit/static/js/chat_feedback.js +0 -115
- iatoolkit/static/js/chat_history.js +0 -127
- iatoolkit/static/styles/chat_info.css +0 -53
- iatoolkit/templates/_branding_styles.html +0 -53
- iatoolkit/templates/_navbar.html +0 -9
- iatoolkit/templates/header.html +0 -31
- iatoolkit/templates/test.html +0 -9
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit-0.59.1.dist-info/RECORD +0 -111
- {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/top_level.txt +0 -0
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
-
const reloadButton = document.getElementById('force-reload-button');
|
|
3
|
-
if (!reloadButton) return;
|
|
4
|
-
|
|
5
|
-
const originalIconClass = 'bi bi-arrow-clockwise';
|
|
6
|
-
const spinnerIconClass = 'spinner-border spinner-border-sm';
|
|
7
|
-
|
|
8
|
-
// Configuración de Toastr para que aparezca abajo a la derecha
|
|
9
|
-
toastr.options = { "positionClass": "toast-bottom-right", "preventDuplicates": true };
|
|
10
|
-
|
|
11
|
-
reloadButton.addEventListener('click', async function(event) {
|
|
12
|
-
event.preventDefault();
|
|
13
|
-
|
|
14
|
-
if (reloadButton.disabled) return; // Prevenir doble clic
|
|
15
|
-
|
|
16
|
-
// 1. Deshabilitar y mostrar spinner
|
|
17
|
-
reloadButton.disabled = true;
|
|
18
|
-
const icon = reloadButton.querySelector('i');
|
|
19
|
-
icon.className = spinnerIconClass;
|
|
20
|
-
toastr.info('Iniciando recarga de contexto en segundo plano...');
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// 2. Definir los parámetros para callToolkit
|
|
24
|
-
const apiPath = '/api/init-context';
|
|
25
|
-
const payload = { 'user_identifier': window.user_identifier };
|
|
26
|
-
|
|
27
|
-
// 3. Hacer la llamada usando callToolkit
|
|
28
|
-
const data = await callToolkit(apiPath, payload, 'POST');
|
|
29
|
-
|
|
30
|
-
// 4. Procesar la respuesta
|
|
31
|
-
// callToolkit devuelve null si hubo un error que ya mostró en el chat.
|
|
32
|
-
if (data) {
|
|
33
|
-
if (data.status === 'OK') {
|
|
34
|
-
toastr.success(data.message || 'Contexto recargado exitosamente.');
|
|
35
|
-
} else {
|
|
36
|
-
// El servidor respondió 200 OK pero con un mensaje de error en el cuerpo
|
|
37
|
-
toastr.error(data.error_message || 'Ocurrió un error desconocido durante la recarga.');
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
// Si data es null, callToolkit ya manejó el error (mostrando un mensaje en el chat).
|
|
41
|
-
// Añadimos un toast para notificar al usuario que algo falló.
|
|
42
|
-
toastr.error('Falló la recarga del contexto. Revisa el chat para más detalles.');
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
// Este bloque se ejecutará para errores no controlados por callToolkit (como AbortError)
|
|
46
|
-
console.error('Error durante la recarga del contexto:', error);
|
|
47
|
-
toastr.error(error.message || 'Error de red al intentar recargar.');
|
|
48
|
-
} finally {
|
|
49
|
-
// 5. Restaurar el botón en cualquier caso
|
|
50
|
-
reloadButton.disabled = false;
|
|
51
|
-
icon.className = originalIconClass;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|
|
@@ -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>×</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
|
-
"user_identifier": window.user_identifier,
|
|
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 callToolkit('/feedback', data, "POST");
|
|
110
|
-
return responseData;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error("Error al enviar feedback:", error);
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,127 +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
|
-
// Evento delegado para el icono de copiar.
|
|
9
|
-
// Se adjunta UNA SOLA VEZ al cuerpo de la tabla y funciona para todas las filas
|
|
10
|
-
// que se añadan dinámicamente.
|
|
11
|
-
$('#history-table-body').on('click', '.copy-query-icon', function() {
|
|
12
|
-
const queryText = $(this).data('query');
|
|
13
|
-
|
|
14
|
-
// Copiar el texto al textarea del chat
|
|
15
|
-
if (queryText) {
|
|
16
|
-
$('#question').val(queryText);
|
|
17
|
-
autoResizeTextarea($('#question')[0]);
|
|
18
|
-
$('#send-button').removeClass('disabled');
|
|
19
|
-
|
|
20
|
-
// Cerrar el modal
|
|
21
|
-
$('#historyModal').modal('hide');
|
|
22
|
-
|
|
23
|
-
// Hacer focus en el textarea
|
|
24
|
-
$('#question').focus();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Variables globales para el historial
|
|
29
|
-
let historyData = [];
|
|
30
|
-
|
|
31
|
-
// Función para cargar el historial
|
|
32
|
-
async function loadHistory() {
|
|
33
|
-
const historyLoading = $('#history-loading');
|
|
34
|
-
const historyError = $('#history-error');
|
|
35
|
-
const historyContent = $('#history-content');
|
|
36
|
-
|
|
37
|
-
// Mostrar loading
|
|
38
|
-
historyLoading.show();
|
|
39
|
-
historyError.hide();
|
|
40
|
-
historyContent.hide();
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const responseData = await callToolkit("/api/history", {}, "POST");
|
|
44
|
-
|
|
45
|
-
if (responseData && responseData.history) {
|
|
46
|
-
// Guardar datos globalmente
|
|
47
|
-
historyData = responseData.history;
|
|
48
|
-
|
|
49
|
-
// Mostrar todos los datos
|
|
50
|
-
displayAllHistory();
|
|
51
|
-
|
|
52
|
-
// Mostrar contenido
|
|
53
|
-
historyContent.show();
|
|
54
|
-
} else {
|
|
55
|
-
throw new Error('La respuesta del servidor no contenía el formato esperado.');
|
|
56
|
-
}
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error("Error al cargar historial:", error);
|
|
59
|
-
|
|
60
|
-
const friendlyErrorMessage = "No hemos podido cargar tu historial en este momento. Por favor, cierra esta ventana y vuelve a intentarlo en unos instantes.";
|
|
61
|
-
const errorHtml = `
|
|
62
|
-
<div class="text-center p-4">
|
|
63
|
-
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 2.5rem; opacity: 0.8;"></i>
|
|
64
|
-
<h5 class="mt-3 mb-2">Ocurrió un Problema</h5>
|
|
65
|
-
<p class="text-muted">${friendlyErrorMessage}</p>
|
|
66
|
-
</div>
|
|
67
|
-
`;
|
|
68
|
-
historyError.html(errorHtml).show();
|
|
69
|
-
} finally {
|
|
70
|
-
historyLoading.hide();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Función para mostrar todo el historial
|
|
75
|
-
function displayAllHistory() {
|
|
76
|
-
const historyTableBody = $('#history-table-body');
|
|
77
|
-
|
|
78
|
-
// Limpiar tabla
|
|
79
|
-
historyTableBody.empty();
|
|
80
|
-
|
|
81
|
-
// Filtrar solo consultas que son strings simples
|
|
82
|
-
const filteredHistory = historyData.filter(item => {
|
|
83
|
-
try {
|
|
84
|
-
JSON.parse(item.query);
|
|
85
|
-
return false;
|
|
86
|
-
} catch (e) {
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Poblar la tabla
|
|
92
|
-
filteredHistory.forEach((item, index) => {
|
|
93
|
-
const icon = $('<i>').addClass('bi bi-pencil-fill');
|
|
94
|
-
|
|
95
|
-
const link = $('<a>')
|
|
96
|
-
.attr('href', 'javascript:void(0);')
|
|
97
|
-
.addClass('copy-query-icon')
|
|
98
|
-
.attr('title', 'Copiar consulta al chat')
|
|
99
|
-
.data('query', item.query)
|
|
100
|
-
.append(icon);
|
|
101
|
-
|
|
102
|
-
const row = $('<tr>').append(
|
|
103
|
-
$('<td>').text(index + 1),
|
|
104
|
-
$('<td>').addClass('date-cell').text(formatDate(item.created_at)),
|
|
105
|
-
$('<td>').text(item.query),
|
|
106
|
-
$('<td>').addClass('text-center').append(link)
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
historyTableBody.append(row);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Función para formatear fecha
|
|
114
|
-
function formatDate(dateString) {
|
|
115
|
-
const date = new Date(dateString);
|
|
116
|
-
|
|
117
|
-
const padTo2Digits = (num) => num.toString().padStart(2, '0');
|
|
118
|
-
|
|
119
|
-
const day = padTo2Digits(date.getDate());
|
|
120
|
-
const month = padTo2Digits(date.getMonth() + 1);
|
|
121
|
-
const year = date.getFullYear();
|
|
122
|
-
const hours = padTo2Digits(date.getHours());
|
|
123
|
-
const minutes = padTo2Digits(date.getMinutes());
|
|
124
|
-
|
|
125
|
-
return `${day}-${month}-${year} ${hours}:${minutes}`;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
@@ -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,53 +0,0 @@
|
|
|
1
|
-
<!-- templates/_branding_styles.html -->
|
|
2
|
-
<style>
|
|
3
|
-
{{ branding.css_variables | safe }}
|
|
4
|
-
|
|
5
|
-
/* Clases de botón reutilizables basadas en el branding */
|
|
6
|
-
.btn-branded-primary {
|
|
7
|
-
background-color: var(--brand-primary-color);
|
|
8
|
-
color: var(--brand-text-on-primary);
|
|
9
|
-
border-color: var(--brand-primary-color);
|
|
10
|
-
transition: all 0.2s ease-in-out;
|
|
11
|
-
}
|
|
12
|
-
.btn-branded-primary:hover {
|
|
13
|
-
color: var(--brand-text-on-primary);
|
|
14
|
-
box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15);
|
|
15
|
-
}
|
|
16
|
-
.btn-branded-secondary {
|
|
17
|
-
background-color: var(--brand-secondary-color);
|
|
18
|
-
color: var(--brand-text-on-secondary);
|
|
19
|
-
border-color: var(--brand-secondary-color);
|
|
20
|
-
transition: all 0.2s ease-in-out;
|
|
21
|
-
}
|
|
22
|
-
.btn-branded-secondary:hover {
|
|
23
|
-
color: var(--brand-text-on-secondary);
|
|
24
|
-
box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15);
|
|
25
|
-
}
|
|
26
|
-
.form-title {
|
|
27
|
-
color: var(--brand-primary-color);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* --- ESTILOS CORREGIDOS Y MÁS ESPECÍFICOS PARA SWEETALERT2 --- */
|
|
31
|
-
|
|
32
|
-
/* Botón de Confirmar (OK) */
|
|
33
|
-
.swal2-confirm.custom-confirm-button {
|
|
34
|
-
background-color: var(--brand-primary-color) !important;
|
|
35
|
-
color: var(--brand-text-on-primary) !important;
|
|
36
|
-
border: 1px solid var(--brand-primary-color) !important;
|
|
37
|
-
box-shadow: none !important;
|
|
38
|
-
}
|
|
39
|
-
.swal2-confirm.custom-confirm-button:hover {
|
|
40
|
-
box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15) !important;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/* Botón de Cancelar */
|
|
44
|
-
.swal2-cancel.custom-cancel-button {
|
|
45
|
-
background-color: var(--brand-secondary-color) !important;
|
|
46
|
-
color: var(--brand-text-on-secondary) !important;
|
|
47
|
-
border: 1px solid var(--brand-secondary-color) !important;
|
|
48
|
-
box-shadow: none !important;
|
|
49
|
-
}
|
|
50
|
-
.swal2-cancel.custom-cancel-button:hover {
|
|
51
|
-
box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.15) !important;
|
|
52
|
-
}
|
|
53
|
-
</style>
|
iatoolkit/templates/_navbar.html
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<!-- templates/_navbar.html -->
|
|
2
|
-
<nav class="navbar landing-navbar">
|
|
3
|
-
<div class="container-fluid">
|
|
4
|
-
<!-- Convertido en un enlace a la landing page de la compañía actual -->
|
|
5
|
-
<a class="navbar-brand text-decoration-none" href="{{ url_for('index', company_short_name=company_short_name) }}">
|
|
6
|
-
IAToolkit
|
|
7
|
-
</a>
|
|
8
|
-
</div>
|
|
9
|
-
</nav>
|
iatoolkit/templates/header.html
DELETED
|
@@ -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 %}
|
iatoolkit/templates/test.html
DELETED
|
@@ -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,83 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from flask.views import MethodView
|
|
7
|
-
from flask import request, jsonify
|
|
8
|
-
from iatoolkit.services.tasks_service import TaskService
|
|
9
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
10
|
-
from injector import inject
|
|
11
|
-
import logging
|
|
12
|
-
from typing import Optional
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TaskReviewView(MethodView):
|
|
16
|
-
@inject
|
|
17
|
-
def __init__(self, task_service: TaskService, profile_repo: ProfileRepo):
|
|
18
|
-
self.task_service = task_service
|
|
19
|
-
self.profile_repo = profile_repo
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _authenticate_requesting_company_via_api_key(self) -> tuple[
|
|
23
|
-
Optional[int], Optional[str], Optional[tuple[dict, int]]]:
|
|
24
|
-
"""
|
|
25
|
-
Autentica a la compañía usando su API Key.
|
|
26
|
-
Retorna (company_id, company_short_name, None) en éxito.
|
|
27
|
-
Retorna (None, None, (error_json, status_code)) en fallo.
|
|
28
|
-
"""
|
|
29
|
-
api_key_header = request.headers.get('Authorization')
|
|
30
|
-
if not api_key_header or not api_key_header.startswith('Bearer '):
|
|
31
|
-
return None, None, ({"error": "API Key faltante o mal formada en el header Authorization"}, 401)
|
|
32
|
-
|
|
33
|
-
api_key_value = api_key_header.split('Bearer ')[1]
|
|
34
|
-
try:
|
|
35
|
-
api_key_entry = self.profile_repo.get_active_api_key_entry(api_key_value)
|
|
36
|
-
if not api_key_entry:
|
|
37
|
-
return None, None, ({"error": "API Key inválida o inactiva"}, 401)
|
|
38
|
-
|
|
39
|
-
# api_key_entry.company ya está cargado por joinedload en get_active_api_key_entry
|
|
40
|
-
if not api_key_entry.company: # Sanity check
|
|
41
|
-
logging.error(
|
|
42
|
-
f"ChatTokenRequest: API Key {api_key_value[:5]}... no tiene compañía asociada a pesar de ser válida.")
|
|
43
|
-
return None, None, ({"error": "Error interno del servidor al verificar API Key"}, 500)
|
|
44
|
-
|
|
45
|
-
return api_key_entry.company_id, api_key_entry.company.short_name, None
|
|
46
|
-
|
|
47
|
-
except Exception as e:
|
|
48
|
-
logging.exception(f"ChatTokenRequest: Error interno durante validación de API Key: {e}")
|
|
49
|
-
return None, None, ({"error": "Error interno del servidor al validar API Key"}, 500)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def post(self, task_id: int):
|
|
53
|
-
try:
|
|
54
|
-
# only requests with valid api-key are allowed
|
|
55
|
-
auth_company_id, auth_company_short_name, error = self._authenticate_requesting_company_via_api_key()
|
|
56
|
-
if error:
|
|
57
|
-
return jsonify(error[0]), error[1]
|
|
58
|
-
|
|
59
|
-
req_data = request.get_json()
|
|
60
|
-
|
|
61
|
-
required_fields = ['review_user', 'approved']
|
|
62
|
-
for field in required_fields:
|
|
63
|
-
if field not in req_data:
|
|
64
|
-
return jsonify({"error": f"El campo {field} es requerido"}), 400
|
|
65
|
-
|
|
66
|
-
review_user = req_data.get('review_user', '')
|
|
67
|
-
approved = req_data.get('approved', False)
|
|
68
|
-
comment = req_data.get('comment', '')
|
|
69
|
-
|
|
70
|
-
new_task = self.task_service.review_task(
|
|
71
|
-
task_id=task_id,
|
|
72
|
-
review_user=review_user,
|
|
73
|
-
approved=approved,
|
|
74
|
-
comment=comment)
|
|
75
|
-
|
|
76
|
-
return jsonify({
|
|
77
|
-
"task_id": new_task.id,
|
|
78
|
-
"status": new_task.status.name
|
|
79
|
-
}), 200
|
|
80
|
-
|
|
81
|
-
except Exception as e:
|
|
82
|
-
logging.exception("Error al revisar la tarea: %s", str(e))
|
|
83
|
-
return jsonify({"error": str(e)}), 500
|