iatoolkit 0.50.2__py3-none-any.whl → 0.55.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.

@@ -26,8 +26,8 @@ def register_views(injector, app):
26
26
  from iatoolkit.views.tasks_view import TaskView
27
27
  from iatoolkit.views.tasks_review_view import TaskReviewView
28
28
  from iatoolkit.views.login_test_view import LoginTest
29
- from iatoolkit.views.login_view import LoginView, InitiateLoginView
30
- from iatoolkit.views.external_login_view import InitiateExternalChatView
29
+ from iatoolkit.views.login_view import LoginView, FinalizeContextView
30
+ from iatoolkit.views.external_login_view import ExternalLoginView
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
@@ -46,8 +46,8 @@ def register_views(injector, app):
46
46
 
47
47
  # this functions are for login external users (with api-key)
48
48
  # only the first one should be used from an external app
49
- app.add_url_rule('/<company_short_name>/initiate_external_chat',
50
- view_func=InitiateExternalChatView.as_view('initiate_external_chat'))
49
+ app.add_url_rule('/<company_short_name>/external_login',
50
+ view_func=ExternalLoginView.as_view('external_login'))
51
51
 
52
52
  # this endpoint is for requesting a chat token for external users
53
53
  app.add_url_rule('/auth/chat_token',
@@ -55,8 +55,8 @@ def register_views(injector, app):
55
55
 
56
56
  # login for the iatoolkit integrated frontend
57
57
  # this is the main login endpoint for the frontend
58
- app.add_url_rule('/<company_short_name>/chat', view_func=InitiateLoginView.as_view('chat'))
59
58
  app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
59
+ app.add_url_rule('/<company_short_name>/finalize_context_load', view_func=FinalizeContextView.as_view('finalize_context_load'))
60
60
 
61
61
  # register new user, account verification and forgot password
62
62
  app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
iatoolkit/iatoolkit.py CHANGED
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
19
19
  from injector import Binder, singleton, Injector
20
20
  from importlib.metadata import version as _pkg_version, PackageNotFoundError
21
21
 
22
- IATOOLKIT_VERSION = "0.21.0"
22
+ IATOOLKIT_VERSION = "0.55.0"
23
23
 
24
24
  # global variable for the unique instance of IAToolkit
25
25
  _iatoolkit_instance: Optional['IAToolkit'] = None
@@ -360,6 +360,10 @@ class IAToolkit:
360
360
  @self.app.context_processor
361
361
  def inject_globals():
362
362
  from iatoolkit.common.session_manager import SessionManager
363
+ from iatoolkit.services.profile_service import ProfileService
364
+
365
+ profile_service = self._injector.get(ProfileService)
366
+ user_profile = profile_service.get_current_session_info().get('profile', {})
363
367
 
364
368
  return {
365
369
  'url_for': url_for,
@@ -367,6 +371,8 @@ class IAToolkit:
367
371
  'app_name': 'IAToolkit',
368
372
  'user_identifier': SessionManager.get('user_identifier'),
369
373
  'company_short_name': SessionManager.get('company_short_name'),
374
+ 'user_is_local': user_profile.get('user_is_local'),
375
+ 'user_email': user_profile.get('user_email'),
370
376
  'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
371
377
  }
372
378
 
@@ -0,0 +1,98 @@
1
+ // static/js/chat_onboarding.js
2
+ (function (global) {
3
+ function qs(root, sel) { return (typeof sel === 'string') ? root.querySelector(sel) : sel; }
4
+
5
+ function createDots(container, count, activeIdx, activeColor) {
6
+ container.innerHTML = '';
7
+ for (let i = 0; i < count; i++) {
8
+ const d = document.createElement('div');
9
+ if (i === activeIdx) d.classList.add('active');
10
+ d.style.width = '10px';
11
+ d.style.height = '10px';
12
+ d.style.borderRadius = '50%';
13
+ d.style.backgroundColor = i === activeIdx ? (activeColor || 'var(--brand-primary-color, #FF5100)') : '#ddd';
14
+ d.style.transition = 'background-color .3s';
15
+ container.appendChild(d);
16
+ }
17
+ }
18
+
19
+ function initOnboarding(opts) {
20
+ const {
21
+ mode = 'modal',
22
+ cards = [],
23
+ ui = {},
24
+ autoRotateMs = 5000,
25
+ shell = {}
26
+ } = opts;
27
+
28
+ const root = document;
29
+ const elIcon = qs(root, ui.icon);
30
+ const elTitle = qs(root, ui.title);
31
+ const elText = qs(root, ui.text);
32
+ const elDots = qs(root, ui.dots);
33
+ const elPrev = qs(root, ui.prev);
34
+ const elNext = qs(root, ui.next);
35
+
36
+ let idx = 0;
37
+ let autoTimer = null;
38
+
39
+ function hasCards() { return Array.isArray(cards) && cards.length > 0; }
40
+
41
+ function render() {
42
+ if (!hasCards()) return;
43
+ const c = cards[idx] || {};
44
+ if (elIcon) elIcon.innerHTML = `<i class="${c.icon || 'bi bi-lightbulb'}"></i>`;
45
+ if (elTitle) elTitle.textContent = c.title || '';
46
+ if (elText) elText.innerHTML = c.text || '';
47
+ if (elDots) createDots(elDots, cards.length, idx);
48
+ }
49
+
50
+ function next() { if (!hasCards()) return; idx = (idx + 1) % cards.length; render(); }
51
+ function prev() { if (!hasCards()) return; idx = (idx - 1 + cards.length) % cards.length; render(); }
52
+
53
+ function startAuto() {
54
+ stopAuto();
55
+ if (!hasCards()) return;
56
+ autoTimer = setInterval(next, autoRotateMs);
57
+ }
58
+ function stopAuto() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } }
59
+
60
+ function setupShellIfNeeded() {
61
+ if (mode !== 'shell') return;
62
+ const loader = ui.loader ? qs(root, ui.loader) : null;
63
+ const container = ui.container ? qs(root, ui.container) : null;
64
+ if (!container || !shell.iframeSrc) return;
65
+
66
+ const iframe = document.createElement('iframe');
67
+ iframe.src = shell.iframeSrc;
68
+ iframe.style.width = '100%';
69
+ iframe.style.height = '100%';
70
+ iframe.style.border = 'none';
71
+ iframe.style.display = 'none';
72
+
73
+ iframe.onload = function () {
74
+ iframe.style.display = 'block';
75
+ if (loader) {
76
+ loader.style.opacity = '0';
77
+ setTimeout(() => loader.style.display = 'none', 500);
78
+ }
79
+ };
80
+ container.appendChild(iframe);
81
+ }
82
+
83
+ if (elPrev) elPrev.addEventListener('click', () => { prev(); startAuto(); });
84
+ if (elNext) elNext.addEventListener('click', () => { next(); startAuto(); });
85
+
86
+ function start() {
87
+ idx = 0;
88
+ render();
89
+ startAuto();
90
+ if (mode === 'shell') setupShellIfNeeded();
91
+ }
92
+
93
+ return { start, stop: stopAuto, next, prev, hasCards };
94
+ }
95
+
96
+ // Export global
97
+ global.initOnboarding = initOnboarding;
98
+ })(window);
@@ -455,4 +455,3 @@
455
455
  #send-button i {
456
456
  color: var(--brand-send-button-color);
457
457
  }
458
-
@@ -0,0 +1,169 @@
1
+ /* static/css/onboarding.css */
2
+
3
+ /* Fuente base para ambos contextos */
4
+ .ob-root {
5
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
6
+ }
7
+
8
+ /* Tarjeta */
9
+ .ob-card {
10
+ background-color: #fff;
11
+ border-radius: 12px;
12
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
13
+ padding: 30px;
14
+ width: 90%;
15
+ max-width: 450px;
16
+ text-align: center;
17
+ transition: opacity 0.3s ease-in-out;
18
+ margin: 0 auto;
19
+ }
20
+
21
+ /* Contenido de tarjeta */
22
+ .ob-icon {
23
+ font-size: 40px;
24
+ color: var(--brand-primary-color, #FF5100);
25
+ margin-bottom: 15px;
26
+ }
27
+ .ob-title {
28
+ font-size: 1.25rem;
29
+ color: #333;
30
+ margin-bottom: 10px;
31
+ }
32
+ .ob-text {
33
+ font-size: 0.95rem;
34
+ color: #666;
35
+ line-height: 1.5;
36
+ min-height: 60px;
37
+ }
38
+
39
+ /* Navegación */
40
+ .ob-nav {
41
+ display: flex;
42
+ justify-content: space-between;
43
+ align-items: center;
44
+ margin-top: 20px;
45
+ }
46
+ .ob-btn {
47
+ background-color: var(--brand-secondary-color, #06326B);
48
+ border: none;
49
+ color: var(--brand-text-on-secondary, #FFFFFF);
50
+ border-radius: 50%;
51
+ width: 40px;
52
+ height: 40px;
53
+ cursor: pointer;
54
+ transition: opacity 0.2s;
55
+ display: inline-flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ }
59
+ .ob-btn:hover { opacity: 0.85; }
60
+
61
+ /* Dots */
62
+ .ob-dots {
63
+ display: flex;
64
+ gap: 8px;
65
+ }
66
+ .ob-dots > div {
67
+ width: 10px;
68
+ height: 10px;
69
+ border-radius: 50%;
70
+ background-color: #ddd;
71
+ transition: background-color 0.3s;
72
+ }
73
+ .ob-dots > div.active {
74
+ background-color: var(--brand-primary-color, #FF5100);
75
+ }
76
+
77
+ /* Encabezado reutilizable (si se usa) */
78
+ .ob-brand-header {
79
+ font-size: 2rem;
80
+ font-weight: 700;
81
+ margin-bottom: 24px;
82
+ color: var(--brand-secondary-color, #06326B);
83
+ text-align: center;
84
+ }
85
+ .ob-brand-header .brand-name {
86
+ color: var(--brand-primary-color, #FF5100);
87
+ }
88
+
89
+ /* Utilidades */
90
+ .ob-fade {
91
+ transition: opacity 0.5s ease-in-out;
92
+ }
93
+
94
+ /* Responsivo */
95
+ @media (max-width: 420px) {
96
+ .ob-card { padding: 24px; }
97
+ .ob-icon { font-size: 34px; }
98
+ }
99
+
100
+ /* Overlay a pantalla completa */
101
+ .onboarding-shell-root {
102
+ position: relative;
103
+ height: calc(100vh - 0px);
104
+ }
105
+
106
+ /* Centrado vertical y horizontal del contenido del loader */
107
+ #loader-wrapper {
108
+ position: absolute; inset: 0;
109
+ background-color: #f4f7f6;
110
+ z-index: 1000;
111
+ display: flex; align-items: center; justify-content: center;
112
+ padding: 20px; box-sizing: border-box;
113
+ transition: opacity 0.5s ease-in-out;
114
+ }
115
+
116
+ /* Pila vertical: header + tarjeta + banda de carga */
117
+ .ob-stack {
118
+ width: 100%;
119
+ max-width: 520px; /* ligeramente más que la tarjeta para respiración */
120
+ display: flex;
121
+ flex-direction: column;
122
+ align-items: stretch; /* la tarjeta ocupa el ancho */
123
+ gap: 16px;
124
+ }
125
+
126
+ /* Header de marca consistente */
127
+ .ob-brand-header {
128
+ font-size: 2rem;
129
+ font-weight: 700;
130
+ margin: 0;
131
+ color: var(--brand-secondary-color, #06326B);
132
+ text-align: center;
133
+ }
134
+ .ob-brand-header .brand-name {
135
+ color: var(--brand-primary-color, #FF5100);
136
+ }
137
+
138
+ /* Banda de estado integrada visualmente con la tarjeta */
139
+ .ob-loading-band {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ gap: 12px;
144
+ padding: 12px 14px;
145
+ background: #ffffff;
146
+ border-radius: 12px;
147
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
148
+ }
149
+
150
+ /* Spinner */
151
+ .spinner {
152
+ width: 28px; height: 28px;
153
+ border: 4px solid rgba(0,0,0,0.1);
154
+ border-top-color: var(--brand-primary-color, #FF5100);
155
+ border-radius: 50%;
156
+ animation: spin 1s linear infinite;
157
+ }
158
+ @keyframes spin { to { transform: rotate(360deg); } }
159
+
160
+ #loading-status p {
161
+ font-size: 0.95rem;
162
+ font-weight: 500;
163
+ color: #555;
164
+ margin: 0;
165
+ }
166
+
167
+ /* Iframe contenedor */
168
+ #content-container { width: 100%; height: 100%; }
169
+ #content-container iframe { width: 100%; height: 100%; border: none; }
@@ -10,7 +10,7 @@
10
10
 
11
11
  <!-- 2. Formulario de Inicio de Sesión -->
12
12
  <form id="login-form"
13
- action="{{ url_for('chat', company_short_name=company_short_name) }}"
13
+ action="{{ url_for('login', company_short_name=company_short_name) }}"
14
14
  method="post">
15
15
  <div class="mb-3">
16
16
  <label for="email" class="form-label d-block">Correo Electrónico</label>
@@ -10,6 +10,7 @@
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
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/onboarding.css', _external=True) }}">
13
14
  <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_modal.css', _external=True) }}">
14
15
  <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}">
15
16
  </head>
@@ -7,6 +7,9 @@
7
7
  <style>
8
8
  {{ branding.css_variables | safe }}
9
9
  </style>
10
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/onboarding.css', _external=True) }}">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
12
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
13
 
11
14
  <!-- Sección de encabezado con el usuario conectado -->
12
15
  <div id="company-section" class="company-section d-flex justify-content-between align-items-center px-3 py-2"
@@ -49,6 +52,11 @@
49
52
  class="ms-3 action-icon-style" title="Tu feedback es muy importante" style="color: {{ branding.header_text_color }};">
50
53
  <i class="bi bi-emoji-smile"></i>
51
54
  </a>
55
+ <a href="javascript:void(0);" id="onboarding-button"
56
+ class="ms-3 action-icon-style" title="Ver onboarding"
57
+ style="color: {{ branding.header_text_color }};">
58
+ <i class="bi bi-lightbulb"></i>
59
+ </a>
52
60
 
53
61
  <!-- Icono de cerrar sesión (al final) -->
54
62
  {% if user_is_local %}
@@ -174,20 +182,16 @@
174
182
  window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
175
183
  window.availablePrompts = {{ prompts.message | tojson }};
176
184
  window.sendButtonColor = "{{ branding.send_button_color }}";
177
-
178
- {% if auth_method == 'jwt' and session_jwt %}
179
- // Store session JWT if it exists, defined in the same global scope
180
- window.sessionJWT = "{{ session_jwt }}";
181
- {% endif %}
185
+ window.onboardingCards = {{ onboarding_cards | tojson }};
182
186
  </script>
183
187
 
184
188
  <!-- Carga de los scripts JS externos después de definir las variables globales -->
189
+ <script src="{{ url_for('static', filename='js/chat_onboarding.js', _external=True) }}"></script>
185
190
  <script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
186
191
  <script src="{{ url_for('static', filename='js/chat_history.js', _external=True) }}"></script>
187
192
  <script src="{{ url_for('static', filename='js/chat_feedback.js', _external=True) }}"></script>
188
193
  <script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
189
194
 
190
-
191
195
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
192
196
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
193
197
  <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
@@ -274,5 +278,44 @@ document.addEventListener('DOMContentLoaded', function() {
274
278
  });
275
279
  });
276
280
  });
281
+
282
+ // Inicialización del modal de onboarding
283
+ document.addEventListener('DOMContentLoaded', function () {
284
+ const btn = document.getElementById('onboarding-button');
285
+ if (!btn) return;
286
+
287
+ const modalEl = document.getElementById('onboardingModal');
288
+ const modal = new bootstrap.Modal(modalEl);
289
+
290
+ if (!window.initOnboarding) {
291
+ console.error('initOnboarding no disponible. Verifica chat_onboarding.js');
292
+ return;
293
+ }
294
+
295
+ const onboarding = initOnboarding({
296
+ mode: 'modal',
297
+ cards: Array.isArray(window.onboardingCards) ? window.onboardingCards : [],
298
+ ui: {
299
+ icon: '#ob-icon',
300
+ title: '#ob-title',
301
+ text: '#ob-text',
302
+ dots: '#ob-dots',
303
+ prev: '#ob-prev',
304
+ next: '#ob-next'
305
+ },
306
+ autoRotateMs: 5000
307
+ });
308
+
309
+ modalEl.addEventListener('hidden.bs.modal', () => onboarding.stop());
310
+
311
+ btn.addEventListener('click', () => {
312
+ if (!onboarding.hasCards()) {
313
+ toastr.info('No hay información de onboarding disponible.');
314
+ return;
315
+ }
316
+ onboarding.start();
317
+ modal.show();
318
+ });
319
+ });
277
320
  </script>
278
321
  {% endblock %}
@@ -109,4 +109,37 @@
109
109
  </div>
110
110
  </div>
111
111
  </div>
112
+ </div>
113
+
114
+ <!-- Modal de Onboarding -->
115
+ <div class="modal fade" id="onboardingModal" tabindex="-1" aria-labelledby="onboardingModalLabel" aria-hidden="true">
116
+ <div class="modal-dialog modal-dialog-centered">
117
+ <div class="modal-content" style="border-radius:12px;">
118
+ <div class="modal-header">
119
+ <h5 class="modal-title w-100 text-center" id="onboardingModalLabel" style="color: var(--brand-primary-color, #FF5100);">
120
+ Bienvenido a {{ branding.name }}
121
+ </h5>
122
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
123
+ </div>
124
+
125
+ <div class="modal-body ob-root">
126
+ <div id="ob-card" class="ob-card">
127
+ <div id="ob-icon" class="ob-icon"><i class="bi bi-lightbulb"></i></div>
128
+ <h6 id="ob-title" class="ob-title"></h6>
129
+ <div id="ob-text" class="ob-text"></div>
130
+ <div class="ob-nav">
131
+ <button id="ob-prev" class="ob-btn" aria-label="Anterior"><i class="bi bi-chevron-left"></i></button>
132
+ <div id="ob-dots" class="ob-dots"></div>
133
+ <button id="ob-next" class="ob-btn" aria-label="Siguiente"><i class="bi bi-chevron-right"></i></button>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="modal-footer">
139
+ <button id="ob-close" type="button" class="btn btn-branded-secondary" data-bs-dismiss="modal">
140
+ Cerrar
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
112
145
  </div>
@@ -72,7 +72,7 @@
72
72
 
73
73
  // Actualizar action del formulario "Iniciar Sesión"
74
74
  if (selectedCompany && selectedCompany.trim() !== '') {
75
- const loginAction = '/' + selectedCompany + '/chat';
75
+ const loginAction = '/' + selectedCompany + '/external_login';
76
76
  $('#login-form').attr('action', loginAction); // Actualizamos la URL del form
77
77
  } else {
78
78
  $('#login-form').attr('action', '#'); // URL genérica si no hay selección
@@ -116,7 +116,7 @@
116
116
  return;
117
117
  }
118
118
 
119
- fetch(`/${selectedCompany}/initiate_external_chat`, {
119
+ fetch(`/${selectedCompany}/external_login`, {
120
120
  method: 'POST',
121
121
  headers: {
122
122
  'Content-Type': 'application/json',
@@ -1,167 +1,101 @@
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 {{ branding.name }} para la IA...</p>
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Iniciando {{ branding.name | default('IAToolkit') }} IA...{% endblock %}
4
+
5
+ {% block styles %}
6
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}?v=6">
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/onboarding.css', _external=True) }}?v=6">
8
+ <style>
9
+ .onboarding-shell-root { position: relative; height: 100vh; }
10
+ .onboarding-shell-root #loader-wrapper { position: absolute; inset: 0; background: #f4f7f6; z-index: 1000; display:flex; align-items:center; justify-content:center; padding:20px; box-sizing:border-box; transition: opacity .5s; }
11
+ .onboarding-shell-root .ob-stack { width:100%; max-width:520px; display:flex; flex-direction:column; gap:16px; }
12
+ .onboarding-shell-root .ob-loading-band { display:flex; align-items:center; justify-content:center; gap:12px; padding:12px 14px; background:#fff; border-radius:12px; box-shadow:0 4px 20px rgba(0,0,0,.08); }
13
+ .onboarding-shell-root .spinner { width:28px; height:28px; border:4px solid rgba(0,0,0,.1); border-top-color: var(--brand-primary-color, #FF5100); border-radius:50%; animation: spin 1s linear infinite; }
14
+ @keyframes spin { to { transform: rotate(360deg); } }
15
+ .onboarding-shell-root #content-container { width:100%; height:100%; }
16
+ .onboarding-shell-root #content-container iframe { width:100%; height:100%; border:none; }
17
+
18
+ /* Forzar colores de marca (sube especificidad y evita overrides) */
19
+ .onboarding-shell-root .ob-brand-header { color: var(--brand-secondary-color, #06326B) !important; }
20
+ .onboarding-shell-root .ob-brand-header .brand-name { color: var(--brand-primary-color, #FF5100) !important; }
21
+ .onboarding-shell-root .ob-icon { color: var(--brand-primary-color, #FF5100) !important; }
22
+ .onboarding-shell-root .ob-btn { background-color: var(--brand-secondary-color, #06326B) !important; color: var(--brand-text-on-secondary, #FFFFFF) !important; border:none !important; }
23
+ .onboarding-shell-root .ob-dots > div.active { background-color: var(--brand-primary-color, #FF5100) !important; }
24
+ </style>
25
+ {% if branding and branding.css_variables %}
26
+ <style>
27
+ {{ branding.css_variables|safe }}
28
+ </style>
29
+ {% endif %}
30
+ {% endblock %}
31
+
32
+ {% block content %}
33
+ <div class="onboarding-shell-root ob-root">
34
+ <div id="loader-wrapper">
35
+ <div class="ob-stack">
36
+ <h1 id="ob-brand-header" class="ob-brand-header">
37
+ <span class="brand-name text-brand-primary">{{ branding.name | default('IAToolkit') }}</span>
38
+ <span class="brand-rest"> IA</span>
39
+ </h1>
40
+
41
+ <div id="card-container" class="ob-card">
42
+ <div id="card-icon" class="ob-icon"><i class="fas fa-lightbulb"></i></div>
43
+ <h3 id="card-title" class="ob-title">Título de la Tarjeta</h3>
44
+ <p id="card-text" class="ob-text">Descripción de la tarjeta de capacitación.</p>
45
+ <div class="ob-nav">
46
+ <button id="prev-card" class="ob-btn btn-branded-primary" aria-label="Anterior">
47
+ <i class="fas fa-chevron-left"></i>
48
+ </button>
49
+ <div id="progress-dots" class="ob-dots"></div>
50
+ <button id="next-card" class="ob-btn btn-branded-primary" aria-label="Siguiente">
51
+ <i class="fas fa-chevron-right"></i>
52
+ </button>
99
53
  </div>
54
+ </div>
100
55
 
56
+ <div id="loading-status" class="ob-loading-band">
57
+ <div class="spinner"></div>
58
+ <p>Inicializando el contexto de {{ branding.name }} para la IA...</p>
59
+ </div>
101
60
  </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);
61
+ </div>
62
+
63
+ <div id="content-container"></div>
64
+ </div>
65
+ {% endblock %}
66
+
67
+ {% block scripts %}
68
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
69
+
70
+ <script src="{{ url_for('static', filename='js/chat_onboarding.js', _external=True) }}"></script>
71
+ <script>
72
+ (function() {
73
+ const cardsData = {{ onboarding_cards | tojson }};
74
+ const iframeSrc = "{{ iframe_src_url }}";
75
+
76
+ if (!window.initOnboarding) {
77
+ console.error('initOnboarding no está disponible. Verifica la carga de chat_onboarding.js');
78
+ return;
79
+ }
80
+
81
+ const onboarding = initOnboarding({
82
+ mode: 'shell',
83
+ cards: cardsData,
84
+ ui: {
85
+ icon: '#card-icon',
86
+ title: '#card-title',
87
+ text: '#card-text',
88
+ dots: '#progress-dots',
89
+ prev: '#prev-card',
90
+ next: '#next-card',
91
+ loader: '#loader-wrapper',
92
+ container: '#content-container'
93
+ },
94
+ autoRotateMs: 5000,
95
+ shell: { iframeSrc: iframeSrc }
164
96
  });
165
- </script>
166
- </body>
167
- </html>
97
+
98
+ onboarding.start();
99
+ })();
100
+ </script>
101
+ {% endblock %}
@@ -0,0 +1,68 @@
1
+ # iatoolkit/views/base_login_view.py
2
+ # Copyright (c) 2024 Fernando Libedinsky
3
+ # Product: IAToolkit
4
+ #
5
+ # IAToolkit is open source software.
6
+
7
+ from flask.views import MethodView
8
+ from flask import render_template, url_for
9
+ from injector import inject
10
+ from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.services.query_service import QueryService
12
+ from iatoolkit.services.branding_service import BrandingService
13
+ from iatoolkit.services.onboarding_service import OnboardingService
14
+ from iatoolkit.services.prompt_manager_service import PromptService
15
+
16
+
17
+ class BaseLoginView(MethodView):
18
+ """
19
+ Base class for views that initiate a session and decide the context
20
+ loading path (fast or slow).
21
+ """
22
+ @inject
23
+ def __init__(self,
24
+ profile_service: ProfileService,
25
+ branding_service: BrandingService,
26
+ prompt_service: PromptService,
27
+ onboarding_service: OnboardingService,
28
+ query_service: QueryService):
29
+ self.profile_service = profile_service
30
+ self.branding_service = branding_service
31
+ self.prompt_service = prompt_service
32
+ self.onboarding_service = onboarding_service
33
+ self.query_service = query_service
34
+
35
+ def _handle_login_path(self, company_short_name: str, user_identifier: str, company):
36
+ """
37
+ Centralized logic to decide between the fast path and the slow path.
38
+ """
39
+ # --- Get the company branding ---
40
+ branding_data = self.branding_service.get_company_branding(company)
41
+
42
+ # this service decides is the context needs to be rebuilt or not
43
+ prep_result = self.query_service.prepare_context(
44
+ company_short_name=company_short_name, user_identifier=user_identifier
45
+ )
46
+ if prep_result.get('rebuild_needed'):
47
+ # --- SLOW PATH: Render the loading shell ---
48
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
49
+
50
+ # callback url to call when the context finish loading
51
+ target_url = url_for('finalize_context_load', company_short_name=company_short_name, _external=True)
52
+
53
+ return render_template(
54
+ "onboarding_shell.html",
55
+ iframe_src_url=target_url,
56
+ branding=branding_data,
57
+ onboarding_cards=onboarding_cards
58
+ )
59
+ else:
60
+ # --- FAST PATH: Render the chat page directly ---
61
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
62
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
63
+ return render_template(
64
+ "chat.html",
65
+ branding=branding_data,
66
+ prompts=prompts,
67
+ onboarding_cards=onboarding_cards
68
+ )
@@ -5,33 +5,41 @@
5
5
 
6
6
  import os
7
7
  import logging
8
- from flask import request, jsonify, render_template, url_for
9
- from flask.views import MethodView
8
+ from flask import request, jsonify
10
9
  from injector import inject
11
10
  from iatoolkit.services.auth_service import AuthService
11
+ from iatoolkit.views.base_login_view import BaseLoginView
12
+
13
+ # Importar los servicios que necesita la clase base
12
14
  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
15
  from iatoolkit.services.branding_service import BrandingService
16
16
  from iatoolkit.services.onboarding_service import OnboardingService
17
+ from iatoolkit.services.query_service import QueryService
18
+ from iatoolkit.services.prompt_manager_service import PromptService
17
19
 
18
-
19
- class InitiateExternalChatView(MethodView):
20
+ class ExternalLoginView(BaseLoginView):
21
+ """
22
+ Handles login for external users via API.
23
+ Authenticates and then delegates the path decision (fast/slow) to the base class.
24
+ """
20
25
  @inject
21
26
  def __init__(self,
22
27
  iauthentication: AuthService,
23
- branding_service: BrandingService,
24
28
  profile_service: ProfileService,
29
+ branding_service: BrandingService,
30
+ prompt_service: PromptService,
25
31
  onboarding_service: OnboardingService,
26
- query_service: QueryService,
27
- prompt_service: PromptService
28
- ):
32
+ query_service: QueryService):
33
+ # Pass the dependencies for the base class to its __init__
34
+ super().__init__(
35
+ profile_service=profile_service,
36
+ branding_service=branding_service,
37
+ onboarding_service=onboarding_service,
38
+ query_service=query_service,
39
+ prompt_service=prompt_service
40
+ )
41
+ # Handle the dependency specific to this child class
29
42
  self.iauthentication = iauthentication
30
- self.branding_service = branding_service
31
- self.profile_service = profile_service
32
- self.onboarding_service = onboarding_service
33
- self.query_service = query_service
34
- self.prompt_service = prompt_service
35
43
 
36
44
  def post(self, company_short_name: str):
37
45
  data = request.get_json()
@@ -51,43 +59,12 @@ class InitiateExternalChatView(MethodView):
51
59
  if not iaut.get("success"):
52
60
  return jsonify(iaut), 401
53
61
 
54
- # 2. Delegate session creation to ProfileService.
62
+ # 2. Create the external user session.
55
63
  self.profile_service.create_external_user_session(company, external_user_id)
56
64
 
57
- # 3. prepare and decide the path
58
- prep_result = self.query_service.prepare_context(
59
- company_short_name=company_short_name, user_identifier=external_user_id
60
- )
61
-
62
- if prep_result.get('rebuild_needed'):
63
- branding_data = self.branding_service.get_company_branding(company)
64
- onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
65
- target_url = url_for('login', company_short_name=company_short_name,
66
- _external=True)
67
-
68
- return render_template(
69
- "onboarding_shell.html",
70
- iframe_src_url=target_url,
71
- branding=branding_data,
72
- onboarding_cards=onboarding_cards
73
- )
74
- else:
75
- # fast path, the context is already on the cache, render the chat directly
76
- try:
77
- session_info = self.profile_service.get_current_session_info()
78
- user_profile = session_info.get('profile', {})
79
-
80
- prompts = self.prompt_service.get_user_prompts(company_short_name)
81
- branding_data = self.branding_service.get_company_branding(company)
82
-
83
- return render_template("chat.html",
84
- company_short_name=company_short_name,
85
- user_is_local=user_profile.get('user_is_local'),
86
- user_email=user_profile.get('user_email'),
87
- branding=branding_data,
88
- prompts=prompts,
89
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
90
- ), 200
91
- except Exception as e:
92
- logging.exception(f"Error en el camino rápido para {company_short_name}/{external_user_id}: {e}")
93
- return jsonify({"error": f"Error interno al iniciar el chat. {str(e)}"}), 500
65
+ # 3. Delegate the path decision to the centralized logic.
66
+ try:
67
+ return self._handle_login_path(company_short_name, external_user_id, company)
68
+ except Exception as e:
69
+ logging.exception(f"Error processing external login path for {company_short_name}/{external_user_id}: {e}")
70
+ return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
@@ -7,24 +7,39 @@ from flask.views import MethodView
7
7
  from flask import render_template, request
8
8
  from injector import inject
9
9
  from iatoolkit.services.profile_service import ProfileService
10
+ from iatoolkit.services.branding_service import BrandingService
11
+ from iatoolkit.services.onboarding_service import OnboardingService
10
12
  import os
11
13
 
12
14
 
13
15
  class LoginTest(MethodView):
14
16
  @inject
15
17
  def __init__(self,
16
- profile_service: ProfileService):
18
+ profile_service: ProfileService,
19
+ branding_service: BrandingService,
20
+ onboarding_service: OnboardingService):
17
21
  self.profile_service = profile_service
22
+ self.branding_service = branding_service
23
+ self.onboarding_service = onboarding_service
18
24
 
19
25
  def get(self):
20
26
  alert_message = request.args.get('alert_message', None)
21
27
  companies = self.profile_service.get_companies()
28
+ branding_data = None
29
+ onboarding_cards = {}
30
+ if companies:
31
+ # Obtener el branding de la primera empresa para la página de prueba
32
+ first_company = companies[0]
33
+ branding_data = self.branding_service.get_company_branding(first_company)
34
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(first_company)
22
35
 
23
36
  # Esta API_KEY para el login
24
37
  api_key_for_login = os.getenv("IATOOLKIT_API_KEY", "tu_api_key_por_defecto_o_error")
25
38
 
26
39
  return render_template('login_test.html',
27
40
  companies=companies,
41
+ branding=branding_data,
42
+ onboarding_cards=onboarding_cards,
28
43
  alert_message=alert_message,
29
44
  alert_icon='success' if alert_message else None,
30
45
  api_key=api_key_for_login
@@ -7,33 +7,19 @@ from flask.views import MethodView
7
7
  from flask import request, redirect, render_template, url_for
8
8
  from injector import inject
9
9
  from iatoolkit.services.profile_service import ProfileService
10
- from iatoolkit.services.prompt_manager_service import PromptService
11
10
  from iatoolkit.services.query_service import QueryService
12
- import os
11
+ from iatoolkit.services.prompt_manager_service import PromptService
13
12
  from iatoolkit.services.branding_service import BrandingService
14
13
  from iatoolkit.services.onboarding_service import OnboardingService
14
+ from iatoolkit.views.base_login_view import BaseLoginView
15
15
 
16
16
 
17
- class InitiateLoginView(MethodView):
17
+ class LoginView(BaseLoginView):
18
18
  """
19
- Handles the initial part of the login process.
20
- Authenticates, decides the login path (fast or slow), and renders
21
- either the chat page directly or the loading shell.
19
+ Handles login for local users.
20
+ Authenticates and then delegates the path decision (fast/slow) to the base class.
22
21
  """
23
22
 
24
- @inject
25
- def __init__(self,
26
- profile_service: ProfileService,
27
- branding_service: BrandingService,
28
- onboarding_service: OnboardingService,
29
- query_service: QueryService,
30
- prompt_service: PromptService):
31
- self.profile_service = profile_service
32
- self.branding_service = branding_service
33
- self.onboarding_service = onboarding_service
34
- self.query_service = query_service
35
- self.prompt_service = prompt_service
36
-
37
23
  def post(self, company_short_name: str):
38
24
  company = self.profile_service.get_company_by_short_name(company_short_name)
39
25
  if not company:
@@ -50,60 +36,31 @@ class InitiateLoginView(MethodView):
50
36
  )
51
37
 
52
38
  if not auth_response['success']:
39
+ branding_data = self.branding_service.get_company_branding(company)
40
+
53
41
  return render_template(
54
42
  'index.html',
55
43
  company_short_name=company_short_name,
56
44
  company=company,
45
+ branding=branding_data,
57
46
  form_data={"email": email},
58
47
  alert_message=auth_response["message"]
59
48
  ), 400
60
49
 
61
50
  user_identifier = auth_response['user_identifier']
62
51
 
63
- # 2. PREPARE and DECIDE: Call prepare_context to determine the path.
64
- prep_result = self.query_service.prepare_context(
65
- company_short_name=company_short_name, user_identifier=user_identifier
66
- )
52
+ # 2. Delegate the path decision to the centralized logic.
53
+ try:
54
+ return self._handle_login_path(company_short_name, user_identifier, company)
55
+ except Exception as e:
56
+ return render_template("error.html", company=company, company_short_name=company_short_name,
57
+ message=f"Error processing login path: {str(e)}"), 500
67
58
 
68
- if prep_result.get('rebuild_needed'):
69
- # --- SLOW PATH: Context rebuild is needed ---
70
- # Render the shell, which will call LoginView for the heavy lifting.
71
- branding_data = self.branding_service.get_company_branding(company)
72
- onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
73
- target_url = url_for('login', company_short_name=company_short_name, _external=True)
74
59
 
75
- return render_template(
76
- "onboarding_shell.html",
77
- iframe_src_url=target_url,
78
- branding=branding_data,
79
- onboarding_cards=onboarding_cards
80
- )
81
- else:
82
- # --- FAST PATH: Context is already cached ---
83
- # Render chat.html directly.
84
- try:
85
- session_info = self.profile_service.get_current_session_info()
86
- user_profile = session_info.get('profile', {})
87
-
88
- prompts = self.prompt_service.get_user_prompts(company_short_name)
89
- branding_data = self.branding_service.get_company_branding(company)
90
-
91
- return render_template("chat.html",
92
- user_is_local=user_profile.get('user_is_local'),
93
- user_email=user_profile.get('user_email'),
94
- branding=branding_data,
95
- prompts=prompts,
96
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
97
- ), 200
98
- except Exception as e:
99
- return render_template("error.html", company=company, company_short_name=company_short_name,
100
- message=f"Error in fast path: {str(e)}"), 500
101
-
102
-
103
- class LoginView(MethodView):
60
+ class FinalizeContextView(MethodView):
104
61
  """
105
- Handles the heavy-lifting part of the login, ONLY triggered by the iframe
106
- in the slow path (when context rebuild is needed).
62
+ Finalizes context loading in the slow path.
63
+ This view is invoked by the iframe inside onboarding_shell.html.
107
64
  """
108
65
 
109
66
  @inject
@@ -111,17 +68,19 @@ class LoginView(MethodView):
111
68
  profile_service: ProfileService,
112
69
  query_service: QueryService,
113
70
  prompt_service: PromptService,
114
- branding_service: BrandingService):
71
+ branding_service: BrandingService,
72
+ onboarding_service: OnboardingService
73
+ ):
115
74
  self.profile_service = profile_service
116
75
  self.query_service = query_service
117
76
  self.prompt_service = prompt_service
118
77
  self.branding_service = branding_service
78
+ self.onboarding_service = onboarding_service
119
79
 
120
80
  def get(self, company_short_name: str):
121
- # 1. Use the new centralized method to get session info.
81
+ # 1. Use the centralized method to get session info.
122
82
  session_info = self.profile_service.get_current_session_info()
123
83
  user_identifier = session_info.get('user_identifier')
124
- user_profile = session_info.get('profile', {})
125
84
 
126
85
  if not user_identifier:
127
86
  # This can happen if the session expires or is invalid.
@@ -132,25 +91,23 @@ class LoginView(MethodView):
132
91
  return render_template('error.html', message="Empresa no encontrada"), 404
133
92
 
134
93
  try:
135
- # 2. Finalize the context rebuild (the potentially long-running task).
136
- # We pass the identifier, and the service resolves if it's local or external.
94
+ # 2. Finalize the context rebuild (the heavy task).
137
95
  self.query_service.finalize_context_rebuild(
138
96
  company_short_name=company_short_name,
139
97
  user_identifier=user_identifier
140
98
  )
141
99
 
142
- # 3. Get the necessary data for the chat page.
100
+ # 3. render the chat page.
143
101
  prompts = self.prompt_service.get_user_prompts(company_short_name)
144
102
  branding_data = self.branding_service.get_company_branding(company)
103
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
145
104
 
146
- # 4. Render the final chat page.
147
- return render_template("chat.html",
148
- user_is_local=user_profile.get('user_is_local'),
149
- user_email=user_profile.get('user_email'),
150
- branding=branding_data,
151
- prompts=prompts,
152
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
153
- ), 200
105
+ return render_template(
106
+ "chat.html",
107
+ branding=branding_data,
108
+ prompts=prompts,
109
+ onboarding_cards=onboarding_cards
110
+ )
154
111
 
155
112
  except Exception as e:
156
113
  return render_template("error.html",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.50.2
3
+ Version: 0.55.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -2,10 +2,10 @@ iatoolkit/__init__.py,sha256=4PWjMJjktixtrxF6BY405qyA50Sv967kEP2x-oil6qk,1120
2
2
  iatoolkit/base_company.py,sha256=uFJmy77LPAceVqkTeuJqo15-auDiq4aTwvC_bbBD0mQ,4607
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=I6IsmzFAUDwTY1fGqozQqjTzPQK1OazE3kxqoddI5PU,16946
5
+ iatoolkit/iatoolkit.py,sha256=jIIhvxu1CwCiz3dLm9B7I2SzH-4sT0QQeci_XD2_C68,17305
6
6
  iatoolkit/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  iatoolkit/common/exceptions.py,sha256=EXx40n5htp7UiOM6P1xfJ9U6NMcADqm62dlFaKz7ICU,1154
8
- iatoolkit/common/routes.py,sha256=VrBMUP0HIEuxrYgtAHfup6a3hXrpoUU85JYqOx2aW4g,6022
8
+ iatoolkit/common/routes.py,sha256=Djf8Q_b5IGkdhQ5Az_AuYIVqam6-HFA2d4ByursdARI,6030
9
9
  iatoolkit/common/session_manager.py,sha256=UeKfD15bcEA3P5e0WSURfotLqpsiIMp3AXxAMhtgHs0,471
10
10
  iatoolkit/common/util.py,sha256=w9dTd3csK0gKtFSp-a4t7XmCPZiYDhiON92uXRbTT8A,14624
11
11
  iatoolkit/infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
@@ -67,34 +67,37 @@ iatoolkit/static/js/chat_feedback.js,sha256=DDT2NPgglrLnW75vtEAVXS72MNt7vlMcavzr
67
67
  iatoolkit/static/js/chat_filepond.js,sha256=mzXafm7a506EpM37KATTK3zvAswO1E0KSUY1vKbwuRc,3163
68
68
  iatoolkit/static/js/chat_history.js,sha256=4hYNODIwYNd5vaQqkR28HZyXYIFKgSayrnmOuT_DUac,4381
69
69
  iatoolkit/static/js/chat_main.js,sha256=j3rbJjWWCEAM1XUXPv6K2SW4S3kBrPAwEzVtLTjHslI,17314
70
- iatoolkit/static/styles/chat_iatoolkit.css,sha256=WgzKKyFRuQU8SozX1sWSN7b66SxVoKIHDwpK6V-xL6g,11417
70
+ iatoolkit/static/js/chat_onboarding.js,sha256=b6ofiFcPhuCaPmSFIvDQZqcMUVvbI7LpIsjZOZJUSAU,3185
71
+ iatoolkit/static/styles/chat_iatoolkit.css,sha256=aA-PZ2TGl_k82JSVVBC2-CJT0NiZAuLOGoiaJhdeVUU,11416
71
72
  iatoolkit/static/styles/chat_info.css,sha256=17DbgoNYE21VYWfb5L9-QLCpD2R1idK4imKRLwXtJLY,1058
72
73
  iatoolkit/static/styles/chat_modal.css,sha256=mdfjrJtmUn3O9rKwIGjJc-oSNmJGnzUY1aAJqEfPh38,4301
73
74
  iatoolkit/static/styles/landing_page.css,sha256=5MHlXkmgZVv9uHE7rZTGYzZeynya3ONY4hp7e2uPXwk,4898
74
75
  iatoolkit/static/styles/llm_output.css,sha256=AlxgRSOleeCk2dLAqFWVaQ-jwZiJjcpC5rHuUv3T6VU,2312
76
+ iatoolkit/static/styles/onboarding.css,sha256=Bo0hd8ngVy404_a-gtNFi-hzljhIAnpE-1oQJGnj0F0,3655
75
77
  iatoolkit/system_prompts/format_styles.prompt,sha256=MSMe1qvR3cF_0IbFshn8R0z6Wx6VCHQq1p37rpu5wwk,3576
76
78
  iatoolkit/system_prompts/query_main.prompt,sha256=D2Wjf0uunQIQsQiJVrY-BTQz6PemM5En6ftmw_c5t4E,2808
77
79
  iatoolkit/system_prompts/sql_rules.prompt,sha256=y4nURVnb9AyFwt-lrbMNBHHtZlhk6kC9grYoOhRnrJo,59174
78
80
  iatoolkit/templates/_branding_styles.html,sha256=x0GJmY1WWpPxKBUoqmxh685_1c6-4uLJWNPU6r81uAc,1912
79
- iatoolkit/templates/_login_widget.html,sha256=c-IKNLITu6-tAM_9hj9NnXh2J8WS6Zy4XaVTvKvnzKg,1851
81
+ iatoolkit/templates/_login_widget.html,sha256=p7Xz0P1Xd3Otn41uVQTA3GmDswMrUVMIn9doDNJojAA,1852
80
82
  iatoolkit/templates/_navbar.html,sha256=o1PvZE5ueLmVpGUAmsjtu-vS_WPROTlJc2sTXl6AS4Y,360
81
83
  iatoolkit/templates/about.html,sha256=ciC08grUVz5qLzdzDDqDX31xirg5PrJIRYabWpV9oA8,294
82
- iatoolkit/templates/base.html,sha256=Bd_dBk_wiAhX0DPQHJAdPtCBBq2xK1J1Z2b9p3Wn0e8,2214
84
+ iatoolkit/templates/base.html,sha256=hHfBqZJsPcZGlb4BxAbHvpJFcSjckLIAVTYTmoyXrz0,2323
83
85
  iatoolkit/templates/change_password.html,sha256=G5a3hYLTpz_5Q_eZ4LNcYSqLeW-CuT4NCHD8bkhAd9k,3573
84
- iatoolkit/templates/chat.html,sha256=mT73hW42QtN2uGciGuwdQQhVi8iQgkQoUWK4cvWZ9Xw,12315
85
- iatoolkit/templates/chat_modals.html,sha256=ngKk0L8qnWteBDLAqCKv8-55LWNH3-HwVyk2of6ylWo,5510
86
+ iatoolkit/templates/chat.html,sha256=spvypAVaVI1I22YRrYgQqzK1lXVPX5ywMp2MNixFTTY,13885
87
+ iatoolkit/templates/chat_modals.html,sha256=o3BmQBCTVun4ukyy-9E0e7FuBlgyVQKbRXuDn86SVnw,6949
86
88
  iatoolkit/templates/error.html,sha256=c3dxieMygsvdjQMiQu_sn6kqqag9zFtVu-z5FunX6so,580
87
89
  iatoolkit/templates/forgot_password.html,sha256=NRZqbNHJXSLNArF_KLbzuem-U57v07awS0ikI_DJbfM,2360
88
90
  iatoolkit/templates/header.html,sha256=179agI7rnYwP_rvJNXIiVde5E8Ec5649_XKq6eew2Hk,1263
89
91
  iatoolkit/templates/index.html,sha256=jBLCqZoIMmUCA1KQESW9BcEgaxE0Rjs-4-i2V0ueSMo,7654
90
- iatoolkit/templates/login_test.html,sha256=b71G5zyKnjDKAZ4DyCpM0diUC38-0WMategv-SKeKfY,5979
91
- iatoolkit/templates/onboarding_shell.html,sha256=at7VXh9vQmDiWu2mJbc6NkzEVlu8I8Xgn6_31JltrW0,7477
92
+ iatoolkit/templates/login_test.html,sha256=HsLgRNowhrg-G7Cyv4MFEYc6m7dJ92OpnQP6ia6eKoM,5981
93
+ iatoolkit/templates/onboarding_shell.html,sha256=WA2-Or91epS2IeWf7m5vcuLYAjtpI36NBCtypcIiXAQ,4546
92
94
  iatoolkit/templates/signup.html,sha256=9ArDvcNQgHFR2dwxy-37AXzGUOeOsT7Nz5u0y6fAB3U,4385
93
95
  iatoolkit/templates/test.html,sha256=rwNtxC83tbCl5COZFXYvmRBxxmgFJtPNuVBd_nq9KWY,133
94
96
  iatoolkit/views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
97
+ iatoolkit/views/base_login_view.py,sha256=6IWJXEg_6K9cPYoKnG_-Kpt_hIdYnv5ZNC5anIrytXk,2819
95
98
  iatoolkit/views/change_password_view.py,sha256=tM0woZyKdhY4XYjS_YXg2sKq3RYkXGfcq_eVAKrNvNM,4498
96
99
  iatoolkit/views/chat_token_request_view.py,sha256=wf32_A2Sq8NHYWshCwL10Tovd1znLoD0jQjzutR3sVE,4408
97
- iatoolkit/views/external_login_view.py,sha256=quXpu8_BJBkZ1VWGxICAu-IcVq45_sU2IxYgN3Ke4K4,4143
100
+ iatoolkit/views/external_login_view.py,sha256=qY5wxh98HyKI0Q1Phf6pkOzRGD7PvbH27GUZn0ccYu4,2889
98
101
  iatoolkit/views/file_store_api_view.py,sha256=Uz9f6sey3_F5K8zuyQz6SwYRKAalCjD1ekf-Mkl_Kfo,2326
99
102
  iatoolkit/views/forgot_password_view.py,sha256=-qKJeeOBqJFdvDUk7rCNg1E1cDQnJQkozPpb0T0FgwA,3159
100
103
  iatoolkit/views/history_api_view.py,sha256=x-tZhB8UzqrD2n-WDIfmHK9iVhGZ9f0yncsGs9mxwt0,1953
@@ -102,15 +105,15 @@ iatoolkit/views/index_view.py,sha256=P5aVdEWxsYOZGbzcXd6WFE733qZ7YXIoeqriUMAM6V8
102
105
  iatoolkit/views/init_context_api_view.py,sha256=1j8NKfODfPrffbA5YO8TPMHh-ildlLNzReIxv_qO-W4,2586
103
106
  iatoolkit/views/llmquery_api_view.py,sha256=Rh-y-VENwwtNsDrYAD_SWKwjK16fW-pFRWlEvI-OYwY,2120
104
107
  iatoolkit/views/llmquery_web_view.py,sha256=WhjlA1mfsoL8hL9tlKQfjCUcaTzT43odlp_uQKmT314,1500
105
- iatoolkit/views/login_test_view.py,sha256=E50Lyhf6EsUilQl9W4SLKIWW39h_Gcyf8Y2rwAJqA-Y,1049
106
- iatoolkit/views/login_view.py,sha256=tQQhYks_4rKKyAzr6ZNuKsc4uHCPDHjZkrMwEBToKzo,6985
108
+ iatoolkit/views/login_test_view.py,sha256=7bqj9XWYCNGZulwPCgBMlk3O1DEIje_A_Hqesa58CVU,1893
109
+ iatoolkit/views/login_view.py,sha256=eAsmiCWS2KSOq3czaxVGm3W9__yaXKxqB9SeuGsdnmI,4635
107
110
  iatoolkit/views/prompt_api_view.py,sha256=MP0r-MiswwKcbNc_5KY7aVbHkrR218I8XCiCX1D0yTA,1244
108
111
  iatoolkit/views/signup_view.py,sha256=BCjhM2lMiDPwYrlW_eEwPl-ZLupblbFfsonWtq0E4vU,3922
109
112
  iatoolkit/views/tasks_review_view.py,sha256=keLsLCyOTTlcoIapnB_lbuSvLwrPVZVpBiFC_7ChbLg,3388
110
113
  iatoolkit/views/tasks_view.py,sha256=a3anTXrJTTvbQuc6PSpOzidLKQFL4hWa7PI2Cppcz8w,4110
111
114
  iatoolkit/views/user_feedback_api_view.py,sha256=59XB9uQLHI4Q6QA4_XhK787HzfXb-c6EY7k1Ccyr4hI,2424
112
115
  iatoolkit/views/verify_user_view.py,sha256=7XLSaxvs8LjBr3cYOUDa9B8DqW_50IGlq0IvmOQcD0Y,2340
113
- iatoolkit-0.50.2.dist-info/METADATA,sha256=2-CxWVuSxylyjv_ze0da9KgTIOmKO0qD7SBjwm0C54Y,9301
114
- iatoolkit-0.50.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- iatoolkit-0.50.2.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
116
- iatoolkit-0.50.2.dist-info/RECORD,,
116
+ iatoolkit-0.55.0.dist-info/METADATA,sha256=thMCV6aAt67vKtHbdiK1zTVTTdW1Q-YC-SWgaSz3ywI,9301
117
+ iatoolkit-0.55.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
+ iatoolkit-0.55.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
119
+ iatoolkit-0.55.0.dist-info/RECORD,,