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 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, short_name: str, name: str, branding: dict | None = None) -> Company:
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
 
@@ -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.external_chat_login_view import InitiateExternalChatView, ExternalChatLoginView
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
- # front if the company internal portal
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('initiate_chat_login'))
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
- app.add_url_rule('/auth/chat_token', view_func=ChatTokenRequestView.as_view('chat-token'))
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"
@@ -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)
@@ -74,6 +74,7 @@ class ProfileRepo:
74
74
  if company:
75
75
  company.parameters = new_company.parameters
76
76
  company.branding = new_company.branding
77
+ company.onboarding_cards = new_company.onboarding_cards
77
78
  else:
78
79
  self.session.add(new_company)
79
80
  company = new_company
@@ -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%;
@@ -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">
@@ -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() {
@@ -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 flask import jsonify
11
- import logging
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
- query_service: QueryService
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.query_service = query_service
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
- def get(self, company_short_name: str, external_user_id: str):
24
- # 1. get access credentials
25
- iaut = self.iauthentication.verify(company_short_name, external_user_id)
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
- # initialize the context
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
- return {'status': 'OK'}, 200
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
- f"Error inesperado al inicializar el contexto durante el login para company {company_short_name}: {e}")
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
@@ -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, profile_service: ProfileService):
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
- "login_shell.html",
61
- data_source_url=url_for('login', company_short_name=company_short_name),
62
- external_user_id='' # Pass an empty string, the shell will handle it
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
- # get company info
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. init the company/user LLM context.
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. get the prompt list from backend
115
+ # 3. Get the prompt list from backend.
104
116
  prompts = self.prompt_service.get_user_prompts(company_short_name)
105
117
 
106
- # 4. get the branding data
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
- company_short_name=company_short_name,
111
- auth_method="Session",
112
- session_jwt=None, # No JWT in this flow
113
- user_email=user_email,
114
- branding=branding_data,
115
- prompts=prompts,
116
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
117
- ), 200
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.12.0
3
+ Version: 0.16.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -1,12 +1,12 @@
1
1
  iatoolkit/__init__.py,sha256=4PWjMJjktixtrxF6BY405qyA50Sv967kEP2x-oil6qk,1120
2
- iatoolkit/base_company.py,sha256=4usUdOC57Nk1XsawAEyC7mlzb0jROadu64YcYf6poiM,4371
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=CCYOpsP7fcpHInrvpSUh6tlZMv1kH8lCmiwi0_aEokU,16723
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=x877TTYj_zfcoVkOXKDRUpTvAehM6JqBguRzgu0LTkM,5093
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=vlSC-Z1cbho5dLrvoMnEroafO7yC3JXsZKn7SqoIXKU,13158
34
- iatoolkit/repositories/profile_repo.py,sha256=X2tnqAdaae-SA9sQq-gBqopVdf77I_Q9J_Aa38P0tjY,4060
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=iYjlUy-5X-x2F6Yg9C70FTMjuItBaUYVHcrXgiKuDbA,4976
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=z1sYkvredKjU4Ag75ZB8CzsRGHi3t69vgxVW6rjSBMU,10073
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=LXXB8oPrcBFkf2pLfOSyAaSh66kHbs4SEujpFL3h9Nw,2163
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=YZmgq8G03wG1Kjq-v3FBZoIeJAI8XFE_2OI6gg-M5RU,9371
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=ev_IvNlLStwF8mrpP2MKUmaQuASK4Z_gB4OorFrCQTw,8232
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/external_chat_login_view.py,sha256=5MwWu_acP5Lra6AHG_xUxoyo7Gb17irDaTrK1i9U6-M,4660
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=Ip4d_3ie9T8soWhznahGbCcARAJfsoFcyvfR0N4XegQ,4921
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.12.0.dist-info/METADATA,sha256=d0z93e4UDV3PgKL1_MYfy4PC__GZo6G_Fy8oSmYctj8,9301
107
- iatoolkit-0.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
108
- iatoolkit-0.12.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
109
- iatoolkit-0.12.0.dist-info/RECORD,,
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