iatoolkit 0.12.0__py3-none-any.whl → 0.16.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/base_company.py +8 -2
- iatoolkit/common/routes.py +8 -10
- iatoolkit/iatoolkit.py +8 -0
- iatoolkit/repositories/models.py +1 -0
- iatoolkit/repositories/profile_repo.py +1 -0
- iatoolkit/services/branding_service.py +24 -0
- iatoolkit/services/onboarding_service.py +43 -0
- iatoolkit/static/styles/chat_iatoolkit.css +22 -8
- iatoolkit/templates/base.html +3 -3
- iatoolkit/templates/chat.html +5 -5
- iatoolkit/templates/home.html +1 -10
- iatoolkit/templates/onboarding_shell.html +167 -0
- iatoolkit/views/external_login_view.py +127 -13
- iatoolkit/views/login_view.py +46 -34
- {iatoolkit-0.12.0.dist-info → iatoolkit-0.16.0.dist-info}/METADATA +1 -1
- {iatoolkit-0.12.0.dist-info → iatoolkit-0.16.0.dist-info}/RECORD +18 -17
- iatoolkit/views/external_chat_login_view.py +0 -112
- {iatoolkit-0.12.0.dist-info → iatoolkit-0.16.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.12.0.dist-info → iatoolkit-0.16.0.dist-info}/top_level.txt +0 -0
iatoolkit/base_company.py
CHANGED
|
@@ -26,10 +26,16 @@ class BaseCompany(ABC):
|
|
|
26
26
|
self.company = self.profile_repo.get_company_by_short_name(short_name)
|
|
27
27
|
return self.company
|
|
28
28
|
|
|
29
|
-
def _create_company(self,
|
|
29
|
+
def _create_company(self,
|
|
30
|
+
short_name: str,
|
|
31
|
+
name: str,
|
|
32
|
+
branding: dict | None = None,
|
|
33
|
+
onboarding_cards: dict | None = None
|
|
34
|
+
) -> Company:
|
|
30
35
|
company_obj = Company(short_name=short_name,
|
|
31
36
|
name=name,
|
|
32
|
-
branding=branding
|
|
37
|
+
branding=branding,
|
|
38
|
+
onboarding_cards=onboarding_cards)
|
|
33
39
|
self.company = self.profile_repo.create_company(company_obj)
|
|
34
40
|
return self.company
|
|
35
41
|
|
iatoolkit/common/routes.py
CHANGED
|
@@ -27,7 +27,7 @@ def register_views(injector, app):
|
|
|
27
27
|
from iatoolkit.views.tasks_review_view import TaskReviewView
|
|
28
28
|
from iatoolkit.views.home_view import HomeView
|
|
29
29
|
from iatoolkit.views.login_view import LoginView, InitiateLoginView
|
|
30
|
-
from iatoolkit.views.
|
|
30
|
+
from iatoolkit.views.external_login_view import InitiateExternalChatView, ExternalChatLoginView
|
|
31
31
|
from iatoolkit.views.signup_view import SignupView
|
|
32
32
|
from iatoolkit.views.verify_user_view import VerifyAccountView
|
|
33
33
|
from iatoolkit.views.forgot_password_view import ForgotPasswordView
|
|
@@ -36,21 +36,19 @@ def register_views(injector, app):
|
|
|
36
36
|
from iatoolkit.views.user_feedback_view import UserFeedbackView
|
|
37
37
|
from iatoolkit.views.prompt_view import PromptView
|
|
38
38
|
from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
|
|
39
|
-
from iatoolkit.views.external_login_view import ExternalLoginView
|
|
40
39
|
from iatoolkit.views.download_file_view import DownloadFileView
|
|
41
40
|
|
|
42
41
|
app.add_url_rule('/', view_func=HomeView.as_view('home'))
|
|
43
42
|
|
|
44
|
-
#
|
|
45
|
-
app.add_url_rule('/<company_short_name>/chat_login', view_func=ExternalChatLoginView.as_view('external_chat_login'))
|
|
46
|
-
app.add_url_rule('/<company_short_name>/external_login/<external_user_id>',
|
|
47
|
-
view_func=ExternalLoginView.as_view('external_login'))
|
|
43
|
+
# login for external portals
|
|
48
44
|
app.add_url_rule('/<company_short_name>/initiate_external_chat',
|
|
49
|
-
view_func=InitiateExternalChatView.as_view('
|
|
45
|
+
view_func=InitiateExternalChatView.as_view('initiate_external_chat'))
|
|
46
|
+
app.add_url_rule('/<company_short_name>/external_login',
|
|
47
|
+
view_func=ExternalChatLoginView.as_view('external_login'))
|
|
48
|
+
app.add_url_rule('/auth/chat_token',
|
|
49
|
+
view_func=ChatTokenRequestView.as_view('chat-token'))
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# main pages for the iatoolkit frontend
|
|
51
|
+
# login for the iatoolkit integrated frontend
|
|
54
52
|
app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
|
|
55
53
|
app.add_url_rule('/<company_short_name>/initiate_login', view_func=InitiateLoginView.as_view('initiate_login'))
|
|
56
54
|
|
iatoolkit/iatoolkit.py
CHANGED
|
@@ -144,8 +144,13 @@ class IAToolkit:
|
|
|
144
144
|
is_https = self._get_config_value('USE_HTTPS', 'false').lower() == 'true'
|
|
145
145
|
is_dev = self._get_config_value('FLASK_ENV') == 'development'
|
|
146
146
|
|
|
147
|
+
# get the iatoolkit domain
|
|
148
|
+
parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
|
|
149
|
+
domain = parsed_url.netloc
|
|
150
|
+
|
|
147
151
|
self.app.config.update({
|
|
148
152
|
'VERSION': self.version,
|
|
153
|
+
'SERVER_NAME': domain,
|
|
149
154
|
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
150
155
|
'SESSION_COOKIE_SAMESITE': "None" if is_https else "Lax",
|
|
151
156
|
'SESSION_COOKIE_SECURE': is_https,
|
|
@@ -156,6 +161,9 @@ class IAToolkit:
|
|
|
156
161
|
'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
|
|
157
162
|
})
|
|
158
163
|
|
|
164
|
+
if parsed_url.scheme == 'https':
|
|
165
|
+
self.app.config['PREFERRED_URL_SCHEME'] = 'https'
|
|
166
|
+
|
|
159
167
|
# Configuración para tokenizers en desarrollo
|
|
160
168
|
if is_dev:
|
|
161
169
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
iatoolkit/repositories/models.py
CHANGED
|
@@ -58,6 +58,7 @@ class Company(Base):
|
|
|
58
58
|
gemini_api_key = Column(String, nullable=True)
|
|
59
59
|
|
|
60
60
|
branding = Column(JSON, nullable=True)
|
|
61
|
+
onboarding_cards = Column(JSON, nullable=True)
|
|
61
62
|
parameters = Column(JSON, nullable=True, default={})
|
|
62
63
|
created_at = Column(DateTime, default=datetime.now)
|
|
63
64
|
allow_jwt = Column(Boolean, default=True, nullable=True)
|
|
@@ -44,6 +44,19 @@ class BrandingService:
|
|
|
44
44
|
"brand_info_text": "#055160", # Texto azul oscuro
|
|
45
45
|
"brand_info_border": "#b6effb",
|
|
46
46
|
|
|
47
|
+
# Estilos para el Asistente de Prompts ---
|
|
48
|
+
"prompt_assistant_bg": "#f8f9fa",
|
|
49
|
+
"prompt_assistant_border": "#dee2e6",
|
|
50
|
+
"prompt_assistant_icon_color": "#6c757d",
|
|
51
|
+
"prompt_assistant_button_bg": "#FFFFFF",
|
|
52
|
+
"prompt_assistant_button_text": "#495057",
|
|
53
|
+
"prompt_assistant_button_border": "#ced4da",
|
|
54
|
+
"prompt_assistant_dropdown_bg": "#f8f9fa",
|
|
55
|
+
"prompt_assistant_header_bg": "#e9ecef",
|
|
56
|
+
"prompt_assistant_header_text": "#495057",
|
|
57
|
+
"prompt_assistant_item_hover_bg": None, # Usará el primario por defecto
|
|
58
|
+
"prompt_assistant_item_hover_text": None, # Usará el texto sobre primario
|
|
59
|
+
|
|
47
60
|
# Color para el botón de Enviar ---
|
|
48
61
|
"send_button_color": "#212529" # Gris oscuro/casi negro por defecto
|
|
49
62
|
}
|
|
@@ -93,6 +106,17 @@ class BrandingService:
|
|
|
93
106
|
--brand-info-bg: {final_branding_values['brand_info_bg']};
|
|
94
107
|
--brand-info-text: {final_branding_values['brand_info_text']};
|
|
95
108
|
--brand-info-border: {final_branding_values['brand_info_border']};
|
|
109
|
+
--brand-prompt-assistant-bg: {final_branding_values['prompt_assistant_bg']};
|
|
110
|
+
--brand-prompt-assistant-border: {final_branding_values['prompt_assistant_border']};
|
|
111
|
+
--brand-prompt-assistant-icon-color: {final_branding_values['prompt_assistant_icon_color']};
|
|
112
|
+
--brand-prompt-assistant-button-bg: {final_branding_values['prompt_assistant_button_bg']};
|
|
113
|
+
--brand-prompt-assistant-button-text: {final_branding_values['prompt_assistant_button_text']};
|
|
114
|
+
--brand-prompt-assistant-button-border: {final_branding_values['prompt_assistant_button_border']};
|
|
115
|
+
--brand-prompt-assistant-dropdown-bg: {final_branding_values['prompt_assistant_dropdown_bg']};
|
|
116
|
+
--brand-prompt-assistant-header-bg: {final_branding_values['prompt_assistant_header_bg']};
|
|
117
|
+
--brand-prompt-assistant-header-text: {final_branding_values['prompt_assistant_header_text']};
|
|
118
|
+
--brand-prompt-assistant-item-hover-bg: {final_branding_values['prompt_assistant_item_hover_bg'] or final_branding_values['brand_primary_color']};
|
|
119
|
+
--brand-prompt-assistant-item-hover-text: {final_branding_values['prompt_assistant_item_hover_text'] or final_branding_values['brand_text_on_primary']};
|
|
96
120
|
|
|
97
121
|
}}
|
|
98
122
|
"""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from iatoolkit.repositories.models import Company
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OnboardingService:
|
|
11
|
+
"""
|
|
12
|
+
Servicio para gestionar las tarjetas de contenido que se muestran
|
|
13
|
+
durante la pantalla de carga (onboarding).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""
|
|
18
|
+
Define el conjunto de tarjetas de onboarding por defecto.
|
|
19
|
+
"""
|
|
20
|
+
self._default_cards = [
|
|
21
|
+
{'icon': 'fas fa-users', 'title': 'Clientes',
|
|
22
|
+
'text': 'Conozco en detalle a nuestros clientes: antigüedad, contactos, historial de operaciones.<br><br><strong>Ejemplo:</strong> ¿cuántos clientes nuevos se incorporaron a mi cartera este año?'},
|
|
23
|
+
{'icon': 'fas fa-cubes', 'title': 'Productos',
|
|
24
|
+
'text': 'Productos: características, condiciones, historial.'},
|
|
25
|
+
|
|
26
|
+
{'icon': 'fas fa-cogs', 'title': 'Personaliza tus Prompts',
|
|
27
|
+
'text': 'Utiliza la varita mágica y podrás explorar los prompts predefinidos que he preparado para ti.'},
|
|
28
|
+
{'icon': 'fas fa-table', 'title': 'Tablas y Excel',
|
|
29
|
+
'text': 'Puedes pedirme la respuesta en formato de tablas o excel.<br><br><strong>Ejemplo:</strong> dame una tabla con los 10 certificados más grandes este año.'},
|
|
30
|
+
{'icon': 'fas fa-shield-alt', 'title': 'Seguridad y Confidencialidad',
|
|
31
|
+
'text': 'Toda tu información es procesada de forma segura y confidencial dentro de nuestro entorno protegido.'}
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
def get_onboarding_cards(self, company: Company | None) -> List[Dict[str, Any]]:
|
|
35
|
+
"""
|
|
36
|
+
Retorna la lista de tarjetas de onboarding para una compañía.
|
|
37
|
+
Si la compañía tiene tarjetas personalizadas, las devuelve.
|
|
38
|
+
De lo contrario, devuelve las tarjetas por defecto.
|
|
39
|
+
"""
|
|
40
|
+
if company and company.onboarding_cards:
|
|
41
|
+
return company.onboarding_cards
|
|
42
|
+
|
|
43
|
+
return self._default_cards
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
|
|
169
169
|
/* 1. La caja principal que envuelve toda el área de entrada */
|
|
170
170
|
.input-area {
|
|
171
|
-
background-color: #f8f9fa;
|
|
171
|
+
background-color: var(--brand-prompt-assistant-bg, #f8f9fa);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/* 2. La barra "cápsula" que envuelve el texto y los iconos */
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
|
|
203
203
|
#prompt-assistant-collapse .card {
|
|
204
204
|
border-radius: 1.5rem; /* Mismo radio que el chat-input-bar */
|
|
205
|
-
border: 1px solid #dee2e6; /* Mismo borde que el chat-input-bar */
|
|
205
|
+
border: 1px solid var(--brand-prompt-assistant-border, #dee2e6); /* Mismo borde que el chat-input-bar */
|
|
206
206
|
box-shadow: none; /* Eliminamos la sombra por defecto del card */
|
|
207
207
|
}
|
|
208
208
|
|
|
@@ -241,6 +241,12 @@
|
|
|
241
241
|
color: #6c757d;
|
|
242
242
|
transition: color 0.2s ease-in-out;
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/* Anulación específica para el icono de la varita mágica */
|
|
246
|
+
.chat-input-bar a[href="#prompt-assistant-collapse"] i {
|
|
247
|
+
color: var(--brand-prompt-assistant-icon-color, #6c757d);
|
|
248
|
+
}
|
|
249
|
+
|
|
244
250
|
.chat-input-bar .d-flex a:hover i {
|
|
245
251
|
color: #343a40;
|
|
246
252
|
}
|
|
@@ -302,8 +308,8 @@
|
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
.dropdown-menu-soft {
|
|
305
|
-
background-color: #f8f9fa;
|
|
306
|
-
border-color: #dee2e6;
|
|
311
|
+
background-color: var(--brand-prompt-assistant-dropdown-bg, #f8f9fa);
|
|
312
|
+
border-color: var(--brand-prompt-assistant-border, #dee2e6);
|
|
307
313
|
}
|
|
308
314
|
|
|
309
315
|
.dropdown-menu-soft .dropdown-item {
|
|
@@ -312,15 +318,15 @@
|
|
|
312
318
|
|
|
313
319
|
.dropdown-menu-soft .dropdown-item:hover,
|
|
314
320
|
.dropdown-menu-soft .dropdown-item:focus {
|
|
315
|
-
color: #ffffff;
|
|
316
|
-
background-color: #495057;
|
|
321
|
+
color: var(--brand-prompt-assistant-item-hover-text, #ffffff);
|
|
322
|
+
background-color: var(--brand-prompt-assistant-item-hover-bg, #495057);
|
|
317
323
|
padding-left: 1.5rem;
|
|
318
324
|
transition: all 0.15s ease-in-out;
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
.dropdown-menu-soft .dropdown-header {
|
|
322
|
-
background-color: #495057;
|
|
323
|
-
color: #ffffff;
|
|
328
|
+
background-color: var(--brand-prompt-assistant-header-bg, #495057);
|
|
329
|
+
color: var(--brand-prompt-assistant-header-text, #ffffff);
|
|
324
330
|
font-weight: 600;
|
|
325
331
|
margin: 4px;
|
|
326
332
|
padding: 0.4rem 1rem;
|
|
@@ -331,6 +337,14 @@
|
|
|
331
337
|
border-bottom: none;
|
|
332
338
|
}
|
|
333
339
|
|
|
340
|
+
/* Estilo para el botón principal del asistente de prompts */
|
|
341
|
+
#prompt-select-button {
|
|
342
|
+
background-color: var(--brand-prompt-assistant-button-bg, #FFFFFF);
|
|
343
|
+
color: var(--brand-prompt-assistant-button-text, #495057);
|
|
344
|
+
border-color: var(--brand-prompt-assistant-button-border, #ced4da);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
334
348
|
#clear-selection-button {
|
|
335
349
|
position: absolute;
|
|
336
350
|
top: 50%;
|
iatoolkit/templates/base.html
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
|
|
10
10
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/filepond/dist/filepond.min.css">
|
|
11
11
|
<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') }}">
|
|
12
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_iatoolkit.css', _external=True) }}">
|
|
13
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_modal.css', _external=True) }}">
|
|
14
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}">
|
|
15
15
|
</head>
|
|
16
16
|
<body class="d-flex flex-column p-3" style="min-height: 100vh;">
|
|
17
17
|
<main class="d-flex flex-column flex-grow-1">
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
<!-- Icono de cerrar sesión (al final) -->
|
|
46
46
|
{% if user_email %}
|
|
47
|
-
<a href="{{ url_for('logout', company_short_name=company_short_name) }}"
|
|
47
|
+
<a href="{{ url_for('logout', company_short_name=company_short_name, _external=True) }}"
|
|
48
48
|
class="ms-3 action-icon-style" title="Cerrar sesión" style="color: {{ branding.header_text_color }} !important;">
|
|
49
49
|
<i class="bi bi-box-arrow-right"></i>
|
|
50
50
|
</a>
|
|
@@ -173,10 +173,10 @@
|
|
|
173
173
|
</script>
|
|
174
174
|
|
|
175
175
|
<!-- Carga de los scripts JS externos después de definir las variables globales -->
|
|
176
|
-
<script src="{{ url_for('static', filename='js/chat_filepond.js') }}"></script>
|
|
177
|
-
<script src="{{ url_for('static', filename='js/chat_history.js') }}"></script>
|
|
178
|
-
<script src="{{ url_for('static', filename='js/chat_feedback.js') }}"></script>
|
|
179
|
-
<script src="{{ url_for('static', filename='js/chat_main.js') }}"></script>
|
|
176
|
+
<script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
|
|
177
|
+
<script src="{{ url_for('static', filename='js/chat_history.js', _external=True) }}"></script>
|
|
178
|
+
<script src="{{ url_for('static', filename='js/chat_feedback.js', _external=True) }}"></script>
|
|
179
|
+
<script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
|
|
180
180
|
|
|
181
181
|
<script>
|
|
182
182
|
document.addEventListener('DOMContentLoaded', function() {
|
iatoolkit/templates/home.html
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<div class="border rounded p-4 shadow-sm bg-light">
|
|
14
14
|
<h4 class="text-muted fw-semibold text-start mb-3">login integrado (IAToolkit)</h4>
|
|
15
15
|
<form id="login-form"
|
|
16
|
-
action="{{ url_for('initiate_login', company_short_name=company_short_name) }}"
|
|
16
|
+
action="{{ url_for('initiate_login', company_short_name=company_short_name, external_login=True) }}"
|
|
17
17
|
method="post">
|
|
18
18
|
<div class="mb-3">
|
|
19
19
|
<label for="company_short_name" class="form-label d-block text-muted">Empresa</label>
|
|
@@ -156,11 +156,6 @@
|
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
const $button = $(this);
|
|
160
|
-
const $spinner = $button.find('.spinner-border');
|
|
161
|
-
$button.prop('disabled', true);
|
|
162
|
-
$spinner.removeClass('d-none');
|
|
163
|
-
|
|
164
159
|
fetch(`/${selectedCompany}/initiate_external_chat`, {
|
|
165
160
|
method: 'POST',
|
|
166
161
|
headers: {
|
|
@@ -188,10 +183,6 @@
|
|
|
188
183
|
.catch(error => {
|
|
189
184
|
Swal.fire({ icon: 'error', title: 'Error de Inicio de Sesión', text: error.message });
|
|
190
185
|
})
|
|
191
|
-
.finally(() => {
|
|
192
|
-
$button.prop('disabled', false);
|
|
193
|
-
$spinner.addClass('d-none');
|
|
194
|
-
});
|
|
195
186
|
|
|
196
187
|
});
|
|
197
188
|
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="es">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Iniciando {{ branding.name | default('IAToolkit') }} IA...</title>
|
|
7
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
|
9
|
+
|
|
10
|
+
<!-- Inyecta las variables CSS de la marca -->
|
|
11
|
+
{% if branding and branding.css_variables %}
|
|
12
|
+
<style>
|
|
13
|
+
{{ branding.css_variables|safe }}
|
|
14
|
+
</style>
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
/* --- Estilos Generales --- */
|
|
19
|
+
body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
|
|
20
|
+
|
|
21
|
+
#loader-wrapper {
|
|
22
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
23
|
+
background-color: #f4f7f6;
|
|
24
|
+
z-index: 1000;
|
|
25
|
+
display: flex;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
align-items: center;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
padding: 20px;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
transition: opacity 0.5s ease-in-out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* --- Estilos de Branding --- */
|
|
35
|
+
#brand-header {
|
|
36
|
+
font-size: 2.5rem;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
margin: 0 0 30px 0; /* Aumentamos el margen inferior para dar espacio */
|
|
39
|
+
color: var(--brand-secondary-color, #06326B);
|
|
40
|
+
}
|
|
41
|
+
#brand-header .brand-name {
|
|
42
|
+
color: var(--brand-primary-color, #FF5100);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/* --- Estilos de la Tarjeta (Sin cambios) --- */
|
|
47
|
+
#card-container {
|
|
48
|
+
background-color: #fff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
49
|
+
padding: 30px; width: 90%; max-width: 450px; text-align: center; transition: opacity 0.3s ease-in-out;
|
|
50
|
+
}
|
|
51
|
+
#card-container .icon { font-size: 40px; color: var(--brand-primary-color, #FF5100); margin-bottom: 15px; }
|
|
52
|
+
#card-container h3 { font-size: 1.25rem; color: #333; margin-bottom: 10px; }
|
|
53
|
+
#card-container p { font-size: 0.95rem; color: #666; line-height: 1.5; min-height: 60px; }
|
|
54
|
+
.card-nav { display: flex; justify-content: space-between; align-items: center; margin-top: 20px; }
|
|
55
|
+
.card-nav button { background-color: var(--brand-secondary-color, #06326B); border: none; color: var(--brand-text-on-secondary, #FFFFFF); border-radius: 50%; width: 40px; height: 40px; cursor: pointer; transition: opacity 0.2s; }
|
|
56
|
+
.card-nav button:hover { opacity: 0.85; }
|
|
57
|
+
#progress-dots { display: flex; gap: 8px; }
|
|
58
|
+
.dot { width: 10px; height: 10px; border-radius: 50%; background-color: #ddd; transition: background-color 0.3s; }
|
|
59
|
+
.dot.active { background-color: var(--brand-primary-color, #FF5100); }
|
|
60
|
+
|
|
61
|
+
/* --- ESTILOS MEJORADOS: SPINNER DE CARGA --- */
|
|
62
|
+
#loading-status { margin-top: 30px; display: flex; align-items: center; gap: 15px; }
|
|
63
|
+
.spinner {
|
|
64
|
+
width: 30px; height: 30px; border: 4px solid rgba(0, 0, 0, 0.1);
|
|
65
|
+
border-top-color: var(--brand-primary-color, #FF5100);
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
animation: spin 1s linear infinite;
|
|
68
|
+
}
|
|
69
|
+
#loading-status p { font-size: 1rem; font-weight: 500; color: #555; margin: 0; }
|
|
70
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
71
|
+
|
|
72
|
+
/* --- Iframe (Sin cambios) --- */
|
|
73
|
+
#content-container { width: 100%; height: 100%; }
|
|
74
|
+
iframe { width: 100%; height: 100%; border: none; }
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<div id="loader-wrapper">
|
|
79
|
+
|
|
80
|
+
<h1 id="brand-header">
|
|
81
|
+
<span class="brand-name">{{ branding.name | default('IAToolkit') }}</span> <span>IA</span>
|
|
82
|
+
</h1>
|
|
83
|
+
|
|
84
|
+
<div id="card-container">
|
|
85
|
+
<div id="card-icon" class="icon"><i class="fas fa-lightbulb"></i></div>
|
|
86
|
+
<h3 id="card-title">Título de la Tarjeta</h3>
|
|
87
|
+
<p id="card-text">Descripción de la tarjeta de capacitación.</p>
|
|
88
|
+
<div class="card-nav">
|
|
89
|
+
<button id="prev-card" aria-label="Anterior"><i class="fas fa-chevron-left"></i></button>
|
|
90
|
+
<div id="progress-dots"></div>
|
|
91
|
+
<button id="next-card" aria-label="Siguiente"><i class="fas fa-chevron-right"></i></button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- MEJORADO: Texto de estado ahora junto al spinner -->
|
|
96
|
+
<div id="loading-status">
|
|
97
|
+
<div class="spinner"></div>
|
|
98
|
+
<p>Inicializando el contexto de Maxxa para la IA...</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div id="content-container"></div>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
$(function() {
|
|
107
|
+
const cardsData = {{ onboarding_cards | tojson }};
|
|
108
|
+
|
|
109
|
+
let currentCardIndex = 0, autoRotateInterval;
|
|
110
|
+
const $cardContainer = $('#card-container'), $cardIcon = $('#card-icon'), $cardTitle = $('#card-title'), $cardText = $('#card-text'), $progressDots = $('#progress-dots');
|
|
111
|
+
function displayCard(index) {
|
|
112
|
+
$cardContainer.css('opacity', 0);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
const card = cardsData[index];
|
|
115
|
+
$cardIcon.html(`<i class="${card.icon}"></i>`);
|
|
116
|
+
$cardTitle.text(card.title);
|
|
117
|
+
$cardText.html(card.text);
|
|
118
|
+
$progressDots.find('.dot').removeClass('active').eq(index).addClass('active');
|
|
119
|
+
$cardContainer.css('opacity', 1);
|
|
120
|
+
}, 300);
|
|
121
|
+
}
|
|
122
|
+
function startAutoRotate() { autoRotateInterval = setInterval(() => $('#next-card').click(), 5000); }
|
|
123
|
+
cardsData.forEach(() => $progressDots.append('<div class="dot"></div>'));
|
|
124
|
+
displayCard(currentCardIndex);
|
|
125
|
+
startAutoRotate();
|
|
126
|
+
$('#next-card').on('click', function() {
|
|
127
|
+
currentCardIndex = (currentCardIndex + 1) % cardsData.length;
|
|
128
|
+
displayCard(currentCardIndex);
|
|
129
|
+
clearInterval(autoRotateInterval); startAutoRotate();
|
|
130
|
+
});
|
|
131
|
+
$('#prev-card').on('click', function() {
|
|
132
|
+
currentCardIndex = (currentCardIndex - 1 + cardsData.length) % cardsData.length;
|
|
133
|
+
displayCard(currentCardIndex);
|
|
134
|
+
clearInterval(autoRotateInterval); startAutoRotate();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const $loader = $('#loader-wrapper');
|
|
138
|
+
const $container = $('#content-container');
|
|
139
|
+
|
|
140
|
+
// URL para el iframe, pasada desde la vista InitiateExternalChatView
|
|
141
|
+
const iframeSrc = "{{ iframe_src_url }}";
|
|
142
|
+
|
|
143
|
+
// Creamos el elemento iframe
|
|
144
|
+
const iframe = document.createElement('iframe');
|
|
145
|
+
iframe.src = iframeSrc;
|
|
146
|
+
|
|
147
|
+
// Estilos para que ocupe toda la pantalla
|
|
148
|
+
iframe.style.width = '100%';
|
|
149
|
+
iframe.style.height = '100%';
|
|
150
|
+
iframe.style.border = 'none';
|
|
151
|
+
iframe.style.display = 'none'; // Empezamos oculto
|
|
152
|
+
|
|
153
|
+
// Evento que se dispara cuando el iframe ha terminado de cargar su contenido
|
|
154
|
+
iframe.onload = function() {
|
|
155
|
+
// Mostramos el iframe
|
|
156
|
+
iframe.style.display = 'block';
|
|
157
|
+
// Ocultamos la animación de carga con una transición suave
|
|
158
|
+
$loader.css('opacity', 0);
|
|
159
|
+
setTimeout(() => $loader.hide(), 500); // Lo eliminamos del DOM después de la transición
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Añadimos el iframe al contenedor en el DOM
|
|
163
|
+
$container.append(iframe);
|
|
164
|
+
});
|
|
165
|
+
</script>
|
|
166
|
+
</body>
|
|
167
|
+
</html>
|
|
@@ -3,38 +3,152 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from flask import request, jsonify, render_template, url_for, session
|
|
6
9
|
from flask.views import MethodView
|
|
7
10
|
from injector import inject
|
|
8
11
|
from iatoolkit.common.auth import IAuthentication
|
|
12
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
9
13
|
from iatoolkit.services.query_service import QueryService
|
|
10
|
-
from
|
|
11
|
-
import
|
|
14
|
+
from iatoolkit.services.prompt_manager_service import PromptService
|
|
15
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
16
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
17
|
+
from iatoolkit.services.onboarding_service import OnboardingService
|
|
18
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
12
19
|
|
|
13
|
-
class ExternalLoginView(MethodView):
|
|
14
20
|
|
|
21
|
+
class InitiateExternalChatView(MethodView):
|
|
15
22
|
@inject
|
|
16
23
|
def __init__(self,
|
|
17
24
|
iauthentication: IAuthentication,
|
|
18
|
-
|
|
25
|
+
branding_service: BrandingService,
|
|
26
|
+
profile_service: ProfileService,
|
|
27
|
+
onboarding_service: OnboardingService,
|
|
28
|
+
jwt_service: JWTService
|
|
19
29
|
):
|
|
20
30
|
self.iauthentication = iauthentication
|
|
21
|
-
self.
|
|
31
|
+
self.branding_service = branding_service
|
|
32
|
+
self.profile_service = profile_service
|
|
33
|
+
self.onboarding_service = onboarding_service
|
|
34
|
+
self.jwt_service = jwt_service
|
|
35
|
+
|
|
36
|
+
def post(self, company_short_name: str):
|
|
37
|
+
data = request.get_json()
|
|
38
|
+
if not data or 'external_user_id' not in data:
|
|
39
|
+
return jsonify({"error": "Falta external_user_id"}), 400
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
external_user_id = data['external_user_id']
|
|
42
|
+
|
|
43
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
44
|
+
if not company:
|
|
45
|
+
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
46
|
+
|
|
47
|
+
# 1. verify access credentials quickly
|
|
48
|
+
iaut = self.iauthentication.verify(
|
|
49
|
+
company_short_name,
|
|
50
|
+
body_external_user_id=external_user_id
|
|
51
|
+
)
|
|
26
52
|
if not iaut.get("success"):
|
|
27
53
|
return jsonify(iaut), 401
|
|
28
54
|
|
|
55
|
+
# 2. Generate a short-lived initiation token.
|
|
56
|
+
initiation_token = self.jwt_service.generate_chat_jwt(
|
|
57
|
+
company_id=company.id,
|
|
58
|
+
company_short_name=company.short_name,
|
|
59
|
+
external_user_id=external_user_id,
|
|
60
|
+
expires_delta_seconds=180
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 2. Get branding and onboarding data for the shell page
|
|
64
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
65
|
+
onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
|
|
66
|
+
|
|
67
|
+
# 4. Generate the URL for the iframe's SRC, now with the secure token.
|
|
68
|
+
target_url = url_for('external_login',
|
|
69
|
+
company_short_name=company_short_name,
|
|
70
|
+
init_token=initiation_token,
|
|
71
|
+
_external=True)
|
|
72
|
+
|
|
73
|
+
# 5. Render the shell.
|
|
74
|
+
return render_template("onboarding_shell.html",
|
|
75
|
+
iframe_src_url=target_url,
|
|
76
|
+
branding=branding_data,
|
|
77
|
+
onboarding_cards=onboarding_cards
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
class ExternalChatLoginView(MethodView):
|
|
81
|
+
@inject
|
|
82
|
+
def __init__(self,
|
|
83
|
+
profile_service: ProfileService,
|
|
84
|
+
query_service: QueryService,
|
|
85
|
+
prompt_service: PromptService,
|
|
86
|
+
iauthentication: IAuthentication,
|
|
87
|
+
jwt_service: JWTService,
|
|
88
|
+
branding_service: BrandingService
|
|
89
|
+
):
|
|
90
|
+
self.profile_service = profile_service
|
|
91
|
+
self.query_service = query_service
|
|
92
|
+
self.prompt_service = prompt_service
|
|
93
|
+
self.iauthentication = iauthentication
|
|
94
|
+
self.jwt_service = jwt_service
|
|
95
|
+
self.branding_service = branding_service
|
|
96
|
+
|
|
97
|
+
def get(self, company_short_name: str):
|
|
98
|
+
# 1. Validate the initiation token from the URL
|
|
99
|
+
init_token = request.args.get('init_token')
|
|
100
|
+
if not init_token:
|
|
101
|
+
return "Falta el token de iniciación.", 401
|
|
102
|
+
|
|
103
|
+
# Reutilizamos el validador de JWT, ya que el token tiene la misma estructura
|
|
104
|
+
payload = self.jwt_service.validate_chat_jwt(init_token, company_short_name)
|
|
105
|
+
if not payload:
|
|
106
|
+
return "Token de iniciación inválido o expirado.", 401
|
|
107
|
+
|
|
108
|
+
# 2. Extract user ID securely from the validated token
|
|
109
|
+
external_user_id = payload.get('external_user_id')
|
|
110
|
+
if not external_user_id:
|
|
111
|
+
return "Token con formato incorrecto.", 400
|
|
112
|
+
|
|
113
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
114
|
+
if not company:
|
|
115
|
+
logging.error(f'Company {company_short_name} not found')
|
|
116
|
+
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
117
|
+
|
|
29
118
|
try:
|
|
30
|
-
#
|
|
119
|
+
# 3. Generate a new long-lived session JWT.
|
|
120
|
+
token = self.jwt_service.generate_chat_jwt(
|
|
121
|
+
company_id=company.id,
|
|
122
|
+
company_short_name=company.short_name,
|
|
123
|
+
external_user_id=external_user_id,
|
|
124
|
+
expires_delta_seconds=3600 * 8 # 8 horas
|
|
125
|
+
)
|
|
126
|
+
if not token:
|
|
127
|
+
raise Exception("No se pudo generar el token de sesión (JWT).")
|
|
128
|
+
|
|
129
|
+
# 4. Init the company/user LLM context.
|
|
31
130
|
self.query_service.llm_init_context(
|
|
32
131
|
company_short_name=company_short_name,
|
|
33
132
|
external_user_id=external_user_id
|
|
34
133
|
)
|
|
35
134
|
|
|
36
|
-
|
|
135
|
+
# 5. get the prompt list from backend
|
|
136
|
+
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
137
|
+
|
|
138
|
+
# 6. get the branding data
|
|
139
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
140
|
+
|
|
141
|
+
# 7. render the chat page with the company/user information.
|
|
142
|
+
return render_template("chat.html",
|
|
143
|
+
company_short_name=company_short_name,
|
|
144
|
+
auth_method='jwt',
|
|
145
|
+
session_jwt=token,
|
|
146
|
+
external_user_id=external_user_id,
|
|
147
|
+
branding=branding_data,
|
|
148
|
+
prompts=prompts,
|
|
149
|
+
iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
|
|
150
|
+
), 200
|
|
151
|
+
|
|
37
152
|
except Exception as e:
|
|
38
|
-
logging.exception(
|
|
39
|
-
|
|
40
|
-
return jsonify({"error_message": str(e)}), 500
|
|
153
|
+
logging.exception(f"Error al inicializar el chat para {company_short_name}/{external_user_id}: {e}")
|
|
154
|
+
return jsonify({"error": "Error interno al iniciar el chat"}), 500
|
iatoolkit/views/login_view.py
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
7
|
from flask import request, redirect, render_template, url_for
|
|
8
8
|
from injector import inject
|
|
9
|
-
from iatoolkit.repositories.models import User
|
|
10
9
|
from iatoolkit.services.profile_service import ProfileService
|
|
11
10
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
12
|
-
from iatoolkit.services.branding_service import BrandingService
|
|
13
11
|
from iatoolkit.services.query_service import QueryService
|
|
14
12
|
import os
|
|
15
13
|
from iatoolkit.common.session_manager import SessionManager
|
|
16
|
-
|
|
14
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
15
|
+
from iatoolkit.services.onboarding_service import OnboardingService
|
|
17
16
|
|
|
18
17
|
class InitiateLoginView(MethodView):
|
|
19
18
|
"""
|
|
@@ -22,8 +21,13 @@ class InitiateLoginView(MethodView):
|
|
|
22
21
|
and immediately returns the loading shell page.
|
|
23
22
|
"""
|
|
24
23
|
@inject
|
|
25
|
-
def __init__(self,
|
|
24
|
+
def __init__(self,
|
|
25
|
+
profile_service: ProfileService,
|
|
26
|
+
branding_service: BrandingService,
|
|
27
|
+
onboarding_service: OnboardingService):
|
|
26
28
|
self.profile_service = profile_service
|
|
29
|
+
self.branding_service = branding_service
|
|
30
|
+
self.onboarding_service = onboarding_service
|
|
27
31
|
|
|
28
32
|
def post(self, company_short_name: str):
|
|
29
33
|
# get company info
|
|
@@ -35,7 +39,7 @@ class InitiateLoginView(MethodView):
|
|
|
35
39
|
email = request.form.get('email')
|
|
36
40
|
password = request.form.get('password')
|
|
37
41
|
|
|
38
|
-
# authenticate the user
|
|
42
|
+
# 1. authenticate the user
|
|
39
43
|
response = self.profile_service.login(
|
|
40
44
|
company_short_name=company_short_name,
|
|
41
45
|
email=email,
|
|
@@ -53,13 +57,22 @@ class InitiateLoginView(MethodView):
|
|
|
53
57
|
},
|
|
54
58
|
alert_message=response["error"]), 400
|
|
55
59
|
|
|
60
|
+
# 2. Get branding and onboarding data for the shell page
|
|
61
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
62
|
+
onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
|
|
63
|
+
|
|
64
|
+
target_url = url_for('login',
|
|
65
|
+
company_short_name=company_short_name,
|
|
66
|
+
_external=True)
|
|
56
67
|
|
|
57
68
|
# 3. Render the shell page, passing the URL for the heavy lifting
|
|
58
69
|
# The shell's AJAX call will now be authenticated via the session cookie.
|
|
59
70
|
return render_template(
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
external_user_id=''
|
|
71
|
+
"onboarding_shell.html",
|
|
72
|
+
iframe_src_url=target_url,
|
|
73
|
+
external_user_id='',
|
|
74
|
+
branding=branding_data,
|
|
75
|
+
onboarding_cards=onboarding_cards
|
|
63
76
|
)
|
|
64
77
|
|
|
65
78
|
|
|
@@ -76,49 +89,48 @@ class LoginView(MethodView):
|
|
|
76
89
|
self.branding_service = branding_service
|
|
77
90
|
|
|
78
91
|
def get(self, company_short_name: str):
|
|
79
|
-
|
|
92
|
+
"""
|
|
93
|
+
Handles the heavy-lifting part of the login, triggered by the iframe.
|
|
94
|
+
The user is already authenticated via the session cookie.
|
|
95
|
+
"""
|
|
96
|
+
# 1. Retrieve user and company info from the session.
|
|
97
|
+
user_id = SessionManager.get('user_id')
|
|
98
|
+
if not user_id:
|
|
99
|
+
# This can happen if the session expires or is invalid.
|
|
100
|
+
# Redirecting to home is a safe fallback.
|
|
101
|
+
return redirect(url_for('home', company_short_name=company_short_name))
|
|
102
|
+
|
|
103
|
+
user_email = SessionManager.get('user')['email']
|
|
80
104
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
81
105
|
if not company:
|
|
82
106
|
return render_template('error.html', message="Empresa no encontrada"), 404
|
|
83
107
|
|
|
84
|
-
return render_template('login.html',
|
|
85
|
-
company=company,
|
|
86
|
-
company_short_name=company_short_name)
|
|
87
|
-
|
|
88
|
-
def post(self, company_short_name: str):
|
|
89
|
-
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
90
|
-
|
|
91
|
-
# 1. The user is already authenticated by the session cookie set by InitiateLoginView.
|
|
92
|
-
# We just retrieve the user and company IDs from the session.
|
|
93
|
-
user_id = SessionManager.get('user_id')
|
|
94
|
-
user_email = SessionManager.get('user')['email']
|
|
95
|
-
|
|
96
108
|
try:
|
|
97
|
-
# 2.
|
|
109
|
+
# 2. Init the company/user LLM context (the long-running task).
|
|
98
110
|
self.query_service.llm_init_context(
|
|
99
111
|
company_short_name=company_short_name,
|
|
100
112
|
local_user_id=user_id
|
|
101
113
|
)
|
|
102
114
|
|
|
103
|
-
# 3.
|
|
115
|
+
# 3. Get the prompt list from backend.
|
|
104
116
|
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
105
117
|
|
|
106
|
-
# 4.
|
|
118
|
+
# 4. Get the branding data.
|
|
107
119
|
branding_data = self.branding_service.get_company_branding(company)
|
|
108
120
|
|
|
121
|
+
# 5. Render the final chat page.
|
|
109
122
|
return render_template("chat.html",
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
company_short_name=company_short_name,
|
|
124
|
+
auth_method="Session",
|
|
125
|
+
session_jwt=None, # No JWT in this flow
|
|
126
|
+
user_email=user_email,
|
|
127
|
+
branding=branding_data,
|
|
128
|
+
prompts=prompts,
|
|
129
|
+
iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
|
|
130
|
+
), 200
|
|
118
131
|
|
|
119
132
|
except Exception as e:
|
|
120
133
|
return render_template("error.html",
|
|
121
134
|
company=company,
|
|
122
135
|
company_short_name=company_short_name,
|
|
123
|
-
message="Ha ocurrido un error inesperado."), 500
|
|
124
|
-
|
|
136
|
+
message="Ha ocurrido un error inesperado."), 500
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
iatoolkit/__init__.py,sha256=4PWjMJjktixtrxF6BY405qyA50Sv967kEP2x-oil6qk,1120
|
|
2
|
-
iatoolkit/base_company.py,sha256=
|
|
2
|
+
iatoolkit/base_company.py,sha256=GacYVVujoxAwUhofRn5eZcR-s1bHtSQXLD-0SRnWSC0,4595
|
|
3
3
|
iatoolkit/cli_commands.py,sha256=G5L9xQXZ0lVFXQWBaE_KEZHyfuiT6PL1nTQRoSdnBzc,2302
|
|
4
4
|
iatoolkit/company_registry.py,sha256=tduqt3oV8iDX_IB1eA7KIgvIxE4edTcy-3qZIXh3Lzw,2549
|
|
5
|
-
iatoolkit/iatoolkit.py,sha256=
|
|
5
|
+
iatoolkit/iatoolkit.py,sha256=9OIBCsHGAQGGIzExP95KFxND1bxaRuYQ08oHkbxv8WM,16996
|
|
6
6
|
iatoolkit/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
iatoolkit/common/auth.py,sha256=8NH6MQXfddLQd1GxrO2op3IYrrP4SMQKoKzj1o1jZmc,8486
|
|
8
8
|
iatoolkit/common/exceptions.py,sha256=EXx40n5htp7UiOM6P1xfJ9U6NMcADqm62dlFaKz7ICU,1154
|
|
9
|
-
iatoolkit/common/routes.py,sha256=
|
|
9
|
+
iatoolkit/common/routes.py,sha256=c3T4YlkYHSLD2OifSI7pfIKy4rgz9xgo5ECcGHbAHNA,4901
|
|
10
10
|
iatoolkit/common/session_manager.py,sha256=7D_RuJs60w-1zDr3fOGEz9JW7IZlSXuUHgUT87CzaUo,472
|
|
11
11
|
iatoolkit/common/util.py,sha256=08js3KLJTXICOd5sgwDp2u_kDaZO_0xG4BIuzWZnLo8,15535
|
|
12
12
|
iatoolkit/infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
@@ -30,13 +30,13 @@ iatoolkit/repositories/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCL
|
|
|
30
30
|
iatoolkit/repositories/database_manager.py,sha256=UaU7k3s7IRuXhCHTy9GoCeP9K1ad0LBdj_n1a_QjGS0,3108
|
|
31
31
|
iatoolkit/repositories/document_repo.py,sha256=Y7bF1kZB1HWJsAGjWdF7P2aVYeTYNufq9ngQXp7mDkY,1124
|
|
32
32
|
iatoolkit/repositories/llm_query_repo.py,sha256=YT_t7cYGQk8rwzH_17-28aTzO-e2jUfa2rvXy8tugvA,3612
|
|
33
|
-
iatoolkit/repositories/models.py,sha256=
|
|
34
|
-
iatoolkit/repositories/profile_repo.py,sha256=
|
|
33
|
+
iatoolkit/repositories/models.py,sha256=DVmFYncVF-JKiE7SZjN8SmsDOsEx1ADlF1PpVnnfASM,13209
|
|
34
|
+
iatoolkit/repositories/profile_repo.py,sha256=vwDuVec9IDUCSyM7ecf790oqdpV36cpM2bgqP8POkHQ,4128
|
|
35
35
|
iatoolkit/repositories/tasks_repo.py,sha256=icVO_r2oPagGnnBhwVFzznnvEEU2EAx-2dlWuWvoDC4,1745
|
|
36
36
|
iatoolkit/repositories/vs_repo.py,sha256=UkpmQQiocgM5IwRBmmWhw3HHzHP6zK1nN3J3TcQgjhc,5300
|
|
37
37
|
iatoolkit/services/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
38
38
|
iatoolkit/services/benchmark_service.py,sha256=CdbFYyS3FHFhNzWQEa9ZNjUlmON10DT1nKNbZQ1EUi8,5880
|
|
39
|
-
iatoolkit/services/branding_service.py,sha256=
|
|
39
|
+
iatoolkit/services/branding_service.py,sha256=gjGKMkCfW9kQJ3Zwzf4XNhrOinfjSo43uD9iG07QlFg,6981
|
|
40
40
|
iatoolkit/services/dispatcher_service.py,sha256=ykR1ye6McyCCuaBgwH6r3-PqcLAr4v4ApkPazMSBzbs,14040
|
|
41
41
|
iatoolkit/services/document_service.py,sha256=nMXrNtbHQuc9pSaten0LvKY0kT8_WngBDmZJUP3jNPw,5936
|
|
42
42
|
iatoolkit/services/excel_service.py,sha256=CJGhu7cQl9J6y_ZWSJ-M63Xm-RXR9Zs66oOR2NJErZQ,3868
|
|
@@ -45,6 +45,7 @@ iatoolkit/services/history_service.py,sha256=ZlYfaSHOcCxxc6ICTnqflaGBlzctpJdNUwX
|
|
|
45
45
|
iatoolkit/services/jwt_service.py,sha256=YoZ9h7_o9xBko-arNQv4MbcwnxoSWVNj4VbZmMo_QGY,3908
|
|
46
46
|
iatoolkit/services/load_documents_service.py,sha256=ZpB0BZ3qX1fGJGBtZtMLbFdWWx0hkPoeCS3OqJKwCTs,7291
|
|
47
47
|
iatoolkit/services/mail_service.py,sha256=2h-fcF3swZDya_o7IpgXkmuj3iEVHVCiHi7oVxU99sQ,2182
|
|
48
|
+
iatoolkit/services/onboarding_service.py,sha256=cMO2Ho1-G3wAeVNl-j25LwCMJjRwj3yKHpYKnZUFLDE,2001
|
|
48
49
|
iatoolkit/services/profile_service.py,sha256=Mxt_Hdz-SyYDuwWVu10hZnb00dKaOeONX8zp1TzpQt4,17387
|
|
49
50
|
iatoolkit/services/prompt_manager_service.py,sha256=U-XmSpkeXvv1KRN4dytdMxSYBMRSB7y-UHcb18mk0nA,8342
|
|
50
51
|
iatoolkit/services/query_service.py,sha256=CI_LUBdqolvsuh7TjY23oaQcc8s6rmmr4LqnjE81ZJc,15394
|
|
@@ -66,7 +67,7 @@ iatoolkit/static/js/chat_feedback.js,sha256=_izl49hFEUZYREcJoaPukpTs0YjDgJYUu-Qf
|
|
|
66
67
|
iatoolkit/static/js/chat_filepond.js,sha256=mzXafm7a506EpM37KATTK3zvAswO1E0KSUY1vKbwuRc,3163
|
|
67
68
|
iatoolkit/static/js/chat_history.js,sha256=G01rKSXOpLpIavycGPbfpfYg5vmPrLhkHYbCLhY3_zs,3964
|
|
68
69
|
iatoolkit/static/js/chat_main.js,sha256=BLKN5a3xP2uu0z6iF6qmgIpT1vClaZf3apcAVQBhU0g,16229
|
|
69
|
-
iatoolkit/static/styles/chat_iatoolkit.css,sha256=
|
|
70
|
+
iatoolkit/static/styles/chat_iatoolkit.css,sha256=TbdviimueZjOKVH5FFSjfIqJvLJJt1XGFRwUJgsIP5M,10887
|
|
70
71
|
iatoolkit/static/styles/chat_info.css,sha256=17DbgoNYE21VYWfb5L9-QLCpD2R1idK4imKRLwXtJLY,1058
|
|
71
72
|
iatoolkit/static/styles/chat_modal.css,sha256=pE7JY5D63Ds_d2FKdmxym4sevvg-2Mf7yo-gB7KA9vE,3730
|
|
72
73
|
iatoolkit/static/styles/llm_output.css,sha256=AlxgRSOleeCk2dLAqFWVaQ-jwZiJjcpC5rHuUv3T6VU,2312
|
|
@@ -74,36 +75,36 @@ iatoolkit/system_prompts/format_styles.prompt,sha256=MSMe1qvR3cF_0IbFshn8R0z6Wx6
|
|
|
74
75
|
iatoolkit/system_prompts/query_main.prompt,sha256=w_9ybgWgiQH4V_RbAXqsvz0M7oOuiyhxcwf-D0CgfA4,3017
|
|
75
76
|
iatoolkit/system_prompts/sql_rules.prompt,sha256=y4nURVnb9AyFwt-lrbMNBHHtZlhk6kC9grYoOhRnrJo,59174
|
|
76
77
|
iatoolkit/templates/about.html,sha256=ciC08grUVz5qLzdzDDqDX31xirg5PrJIRYabWpV9oA8,294
|
|
77
|
-
iatoolkit/templates/base.html,sha256=
|
|
78
|
+
iatoolkit/templates/base.html,sha256=TojvSnVvXkTe7Kpt_BBWoXFfZN6dveKD0VqQjUOXdgU,2212
|
|
78
79
|
iatoolkit/templates/change_password.html,sha256=DFfQSFcZ2YJZNFis2IXfzEKStxTf4i9f4eQ_6GiyNs8,2342
|
|
79
|
-
iatoolkit/templates/chat.html,sha256=
|
|
80
|
+
iatoolkit/templates/chat.html,sha256=pQm1vDikaSsvleJK0LglL1BAeoLbwPLe4FYtLIsW4T8,9451
|
|
80
81
|
iatoolkit/templates/chat_modals.html,sha256=3CQ430bwhebq6rAJ6Bk12PQDjt9YenqNXm5thC5WP2Y,5771
|
|
81
82
|
iatoolkit/templates/error.html,sha256=BNF-7z8AYL5vF4ZMUFMrOBt8c85kCFrm9qSHn9EiHWg,540
|
|
82
83
|
iatoolkit/templates/forgot_password.html,sha256=1lUbKg9CKnQdnySplceY_pibwYne1-mOlM38fqI1kW8,1563
|
|
83
84
|
iatoolkit/templates/header.html,sha256=179agI7rnYwP_rvJNXIiVde5E8Ec5649_XKq6eew2Hk,1263
|
|
84
|
-
iatoolkit/templates/home.html,sha256=
|
|
85
|
+
iatoolkit/templates/home.html,sha256=Bq4wSWrd9E2VRFQ4Br_nqbhipYxZTJfRMiD0hjaSNsM,7942
|
|
85
86
|
iatoolkit/templates/login.html,sha256=r4hy7MsQkfDqi6pBRNkkRiFr3GPSoHCT89R5lQLUWZc,1991
|
|
87
|
+
iatoolkit/templates/onboarding_shell.html,sha256=iIYEe0O3RQ5FgjPigGh8SfC3bFYcJjtd2HeoibyDFLM,7463
|
|
86
88
|
iatoolkit/templates/signup.html,sha256=J8wOjUhUe_KozyThDTWHjXpSJ1ubR2IDVobThtkSRuo,3819
|
|
87
89
|
iatoolkit/templates/test.html,sha256=rwNtxC83tbCl5COZFXYvmRBxxmgFJtPNuVBd_nq9KWY,133
|
|
88
90
|
iatoolkit/views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
89
91
|
iatoolkit/views/change_password_view.py,sha256=rSebwecI1zwBgR2yvAhcfMwWpGDa4QbVAIllgtSOo9k,3940
|
|
90
92
|
iatoolkit/views/chat_token_request_view.py,sha256=wf32_A2Sq8NHYWshCwL10Tovd1znLoD0jQjzutR3sVE,4408
|
|
91
93
|
iatoolkit/views/download_file_view.py,sha256=1gZ0ipqeCn39sTrJFo1-tlewlcSF7s_YNTvE4qd0HOw,2010
|
|
92
|
-
iatoolkit/views/
|
|
93
|
-
iatoolkit/views/external_login_view.py,sha256=EHxN2omqTDkWyMp0FVCNDFt-JJ2hpg8LvprKanGR1gY,1355
|
|
94
|
+
iatoolkit/views/external_login_view.py,sha256=IMfMpL_5xw3m6q9eK7lZ4PhwcL2zW7s7lNIK_HEl78c,6551
|
|
94
95
|
iatoolkit/views/file_store_view.py,sha256=hUm5wX4E5oqJJEPEAObEj-nPiRp5EJIICULSfAWmHCs,1933
|
|
95
96
|
iatoolkit/views/forgot_password_view.py,sha256=Rk8Qbe9Fz7Wlgje1rt29I15gFM-a089EBi2at4FT7kA,2715
|
|
96
97
|
iatoolkit/views/history_view.py,sha256=fzZrnC-RySa7ngcPe2Hmf9_s3imx6VB6MKROMcNpjoU,2064
|
|
97
98
|
iatoolkit/views/home_view.py,sha256=TihO2flkelJa9j6a0FKCMVhD-2X7BhemonB7LTne4x8,1248
|
|
98
99
|
iatoolkit/views/llmquery_view.py,sha256=rv2i3oeXlNc3Sv7Qu3DZGf37r-bMSa4N25FzG7_kPAI,2432
|
|
99
|
-
iatoolkit/views/login_view.py,sha256=
|
|
100
|
+
iatoolkit/views/login_view.py,sha256=tSJiSTT7gKHediAAD2IGdVTUfkTNI1aWMt-d9J-S4lk,5562
|
|
100
101
|
iatoolkit/views/prompt_view.py,sha256=l8KHlLmkSgSLK43VbhwKED7mCN9YyfeHHh4zvx0pT0E,1257
|
|
101
102
|
iatoolkit/views/signup_view.py,sha256=NTx_2w8F6Np88FKEpDvBvJXU-bISKpDMdhhT4XFAVfk,3805
|
|
102
103
|
iatoolkit/views/tasks_review_view.py,sha256=keLsLCyOTTlcoIapnB_lbuSvLwrPVZVpBiFC_7ChbLg,3388
|
|
103
104
|
iatoolkit/views/tasks_view.py,sha256=a3anTXrJTTvbQuc6PSpOzidLKQFL4hWa7PI2Cppcz8w,4110
|
|
104
105
|
iatoolkit/views/user_feedback_view.py,sha256=G37zmP8P4LvZrSymNJ5iFXhLZg1A3BEwRfTpH1Iam5w,2652
|
|
105
106
|
iatoolkit/views/verify_user_view.py,sha256=a3q4wHJ8mKAEmgbNTOcnX4rMikROjOR3mHvCr30qGGA,2351
|
|
106
|
-
iatoolkit-0.
|
|
107
|
-
iatoolkit-0.
|
|
108
|
-
iatoolkit-0.
|
|
109
|
-
iatoolkit-0.
|
|
107
|
+
iatoolkit-0.16.0.dist-info/METADATA,sha256=G6vbKnCI6sqku8SXqzZhzHqSZyBWQnaCM-72AQILHvw,9301
|
|
108
|
+
iatoolkit-0.16.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
109
|
+
iatoolkit-0.16.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
|
|
110
|
+
iatoolkit-0.16.0.dist-info/RECORD,,
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import logging
|
|
8
|
-
from flask import request, jsonify, render_template, url_for, session
|
|
9
|
-
from flask.views import MethodView
|
|
10
|
-
from injector import inject
|
|
11
|
-
from iatoolkit.common.auth import IAuthentication
|
|
12
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
13
|
-
from iatoolkit.services.query_service import QueryService
|
|
14
|
-
from iatoolkit.services.prompt_manager_service import PromptService
|
|
15
|
-
from iatoolkit.services.jwt_service import JWTService
|
|
16
|
-
from iatoolkit.services.branding_service import BrandingService
|
|
17
|
-
|
|
18
|
-
class InitiateExternalChatView(MethodView):
|
|
19
|
-
@inject
|
|
20
|
-
def __init__(self,
|
|
21
|
-
iauthentication: IAuthentication,
|
|
22
|
-
):
|
|
23
|
-
self.iauthentication = iauthentication
|
|
24
|
-
|
|
25
|
-
def post(self, company_short_name: str):
|
|
26
|
-
data = request.get_json()
|
|
27
|
-
if not data or 'external_user_id' not in data:
|
|
28
|
-
return jsonify({"error": "Falta external_user_id"}), 400
|
|
29
|
-
|
|
30
|
-
external_user_id = data['external_user_id']
|
|
31
|
-
|
|
32
|
-
# 1. verify access credentials quickly
|
|
33
|
-
iaut = self.iauthentication.verify(
|
|
34
|
-
company_short_name,
|
|
35
|
-
body_external_user_id=external_user_id
|
|
36
|
-
)
|
|
37
|
-
if not iaut.get("success"):
|
|
38
|
-
return jsonify(iaut), 401
|
|
39
|
-
|
|
40
|
-
# 2. Render the shell page, passing the final data URL and user id
|
|
41
|
-
return render_template("login_shell.html",
|
|
42
|
-
data_source_url=url_for('external_chat_login', company_short_name=company_short_name),
|
|
43
|
-
external_user_id=external_user_id
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
class ExternalChatLoginView(MethodView):
|
|
47
|
-
@inject
|
|
48
|
-
def __init__(self,
|
|
49
|
-
profile_service: ProfileService,
|
|
50
|
-
query_service: QueryService,
|
|
51
|
-
prompt_service: PromptService,
|
|
52
|
-
iauthentication: IAuthentication,
|
|
53
|
-
jwt_service: JWTService,
|
|
54
|
-
branding_service: BrandingService
|
|
55
|
-
):
|
|
56
|
-
self.profile_service = profile_service
|
|
57
|
-
self.query_service = query_service
|
|
58
|
-
self.prompt_service = prompt_service
|
|
59
|
-
self.iauthentication = iauthentication
|
|
60
|
-
self.jwt_service = jwt_service
|
|
61
|
-
self.branding_service = branding_service
|
|
62
|
-
|
|
63
|
-
def post(self, company_short_name: str):
|
|
64
|
-
data = request.get_json()
|
|
65
|
-
if not data or 'external_user_id' not in data:
|
|
66
|
-
return jsonify({"error": "Falta external_user_id"}), 400
|
|
67
|
-
|
|
68
|
-
external_user_id = data['external_user_id']
|
|
69
|
-
|
|
70
|
-
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
71
|
-
if not company:
|
|
72
|
-
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
|
|
76
|
-
# 1. generate a new JWT, our secure access token.
|
|
77
|
-
token = self.jwt_service.generate_chat_jwt(
|
|
78
|
-
company_id=company.id,
|
|
79
|
-
company_short_name=company.short_name,
|
|
80
|
-
external_user_id=external_user_id,
|
|
81
|
-
expires_delta_seconds=3600 * 8 # 8 horas
|
|
82
|
-
)
|
|
83
|
-
if not token:
|
|
84
|
-
raise Exception("No se pudo generar el token de sesión (JWT).")
|
|
85
|
-
|
|
86
|
-
# 2. init the company/user LLM context.
|
|
87
|
-
self.query_service.llm_init_context(
|
|
88
|
-
company_short_name=company_short_name,
|
|
89
|
-
external_user_id=external_user_id
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# 3. get the prompt list from backend
|
|
93
|
-
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
94
|
-
|
|
95
|
-
# 4. get the branding data
|
|
96
|
-
branding_data = self.branding_service.get_company_branding(company)
|
|
97
|
-
|
|
98
|
-
# 5. render the chat page with the company/user information.
|
|
99
|
-
chat_html = render_template("chat.html",
|
|
100
|
-
company_short_name=company_short_name,
|
|
101
|
-
auth_method='jwt', # login method is JWT
|
|
102
|
-
session_jwt=token, # pass the token to the front-end
|
|
103
|
-
external_user_id=external_user_id,
|
|
104
|
-
branding=branding_data,
|
|
105
|
-
prompts=prompts,
|
|
106
|
-
iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
|
|
107
|
-
)
|
|
108
|
-
return chat_html, 200
|
|
109
|
-
|
|
110
|
-
except Exception as e:
|
|
111
|
-
logging.exception(f"Error al inicializar el chat para {company_short_name}/{external_user_id}: {e}")
|
|
112
|
-
return jsonify({"error": "Error interno al iniciar el chat"}), 500
|
|
File without changes
|
|
File without changes
|