iatoolkit 0.71.4__py3-none-any.whl → 0.91.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. iatoolkit/__init__.py +15 -5
  2. iatoolkit/base_company.py +4 -58
  3. iatoolkit/cli_commands.py +6 -7
  4. iatoolkit/common/exceptions.py +1 -0
  5. iatoolkit/common/routes.py +12 -28
  6. iatoolkit/common/util.py +7 -1
  7. iatoolkit/company_registry.py +50 -14
  8. iatoolkit/{iatoolkit.py → core.py} +54 -55
  9. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  10. iatoolkit/infra/llm_client.py +9 -5
  11. iatoolkit/locales/en.yaml +10 -2
  12. iatoolkit/locales/es.yaml +171 -162
  13. iatoolkit/repositories/database_manager.py +59 -14
  14. iatoolkit/repositories/llm_query_repo.py +34 -22
  15. iatoolkit/repositories/models.py +16 -18
  16. iatoolkit/repositories/profile_repo.py +5 -10
  17. iatoolkit/repositories/vs_repo.py +9 -4
  18. iatoolkit/services/auth_service.py +1 -1
  19. iatoolkit/services/branding_service.py +1 -1
  20. iatoolkit/services/company_context_service.py +19 -11
  21. iatoolkit/services/configuration_service.py +219 -46
  22. iatoolkit/services/dispatcher_service.py +31 -225
  23. iatoolkit/services/document_service.py +10 -1
  24. iatoolkit/services/embedding_service.py +9 -6
  25. iatoolkit/services/excel_service.py +50 -2
  26. iatoolkit/services/history_manager_service.py +189 -0
  27. iatoolkit/services/jwt_service.py +1 -1
  28. iatoolkit/services/language_service.py +8 -2
  29. iatoolkit/services/license_service.py +82 -0
  30. iatoolkit/services/mail_service.py +171 -25
  31. iatoolkit/services/profile_service.py +37 -32
  32. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
  33. iatoolkit/services/query_service.py +192 -191
  34. iatoolkit/services/sql_service.py +63 -12
  35. iatoolkit/services/tool_service.py +231 -0
  36. iatoolkit/services/user_feedback_service.py +18 -6
  37. iatoolkit/services/user_session_context_service.py +18 -0
  38. iatoolkit/static/images/iatoolkit_core.png +0 -0
  39. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  40. iatoolkit/static/js/chat_feedback_button.js +1 -1
  41. iatoolkit/static/js/chat_help_content.js +4 -4
  42. iatoolkit/static/js/chat_main.js +17 -5
  43. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  44. iatoolkit/static/styles/chat_iatoolkit.css +1 -1
  45. iatoolkit/static/styles/chat_public.css +28 -0
  46. iatoolkit/static/styles/documents.css +598 -0
  47. iatoolkit/static/styles/landing_page.css +223 -7
  48. iatoolkit/system_prompts/__init__.py +0 -0
  49. iatoolkit/system_prompts/query_main.prompt +2 -1
  50. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  51. iatoolkit/templates/_company_header.html +30 -5
  52. iatoolkit/templates/_login_widget.html +3 -3
  53. iatoolkit/templates/chat.html +1 -1
  54. iatoolkit/templates/forgot_password.html +3 -2
  55. iatoolkit/templates/onboarding_shell.html +1 -1
  56. iatoolkit/templates/signup.html +3 -0
  57. iatoolkit/views/base_login_view.py +1 -1
  58. iatoolkit/views/change_password_view.py +1 -1
  59. iatoolkit/views/forgot_password_view.py +9 -4
  60. iatoolkit/views/history_api_view.py +3 -3
  61. iatoolkit/views/home_view.py +4 -2
  62. iatoolkit/views/init_context_api_view.py +1 -1
  63. iatoolkit/views/llmquery_api_view.py +4 -3
  64. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
  65. iatoolkit/views/login_view.py +17 -5
  66. iatoolkit/views/logout_api_view.py +10 -2
  67. iatoolkit/views/prompt_api_view.py +1 -1
  68. iatoolkit/views/root_redirect_view.py +22 -0
  69. iatoolkit/views/signup_view.py +12 -4
  70. iatoolkit/views/static_page_view.py +27 -0
  71. iatoolkit/views/verify_user_view.py +1 -1
  72. iatoolkit-0.91.1.dist-info/METADATA +268 -0
  73. iatoolkit-0.91.1.dist-info/RECORD +125 -0
  74. iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  75. iatoolkit/services/history_service.py +0 -37
  76. iatoolkit/templates/about.html +0 -13
  77. iatoolkit/templates/index.html +0 -145
  78. iatoolkit/templates/login_simulation.html +0 -45
  79. iatoolkit/views/external_login_view.py +0 -73
  80. iatoolkit/views/index_view.py +0 -14
  81. iatoolkit/views/login_simulation_view.py +0 -93
  82. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  83. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  84. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
  85. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
  86. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/top_level.txt +0 -0
@@ -36,9 +36,16 @@ body {
36
36
  color: #ffffff;
37
37
  }
38
38
 
39
+ /* --- Header & Link Styles --- */
40
+ .website-header .website-brand,
41
+ .website-header a.website-brand,
42
+ .website-header a.website-brand:hover {
43
+ text-decoration: none; /* Elimina el subrayado de los enlaces */
44
+ }
45
+
46
+
39
47
  /* --- Sección Principal (Hero) --- */
40
48
  .hero-section {
41
- padding: 2rem 0 1rem; /* Ajustado: 5rem arriba, 0 a los lados, 2.5rem abajo */
42
49
  }
43
50
 
44
51
  .hero-title {
@@ -54,21 +61,29 @@ body {
54
61
  background-clip: text;
55
62
  text-fill-color: transparent;
56
63
  }
64
+
57
65
  .hero-bullets {
58
66
  list-style: none;
59
67
  padding-left: 0;
60
- font-size: 1.1rem;
68
+ font-size: 1.25rem;
69
+ line-height: 1.5;
70
+ margin-top: 1.5rem;
61
71
  }
72
+
62
73
  .hero-bullets li {
63
74
  display: flex;
64
- align-items: center;
65
- gap: 0.75rem;
66
- margin-bottom: 1rem;
75
+ align-items: flex-start; /* necesario para alinear texto superior */
76
+ gap: 1rem;
77
+ margin-bottom: 1.4rem;
67
78
  color: var(--website-muted-text);
68
79
  }
80
+
69
81
  .hero-bullets .bi {
82
+ font-size: 2rem;
70
83
  color: var(--website-primary-color);
71
- font-size: 1.5rem;
84
+ flex-shrink: 0;
85
+ line-height: 1; /* evita que el ícono genere espacio extra */
86
+ margin-top: 0.35rem; /* ESTE valor alinea perfecto el ícono con el texto */
72
87
  }
73
88
 
74
89
  /* --- Botón de Llamada a la Acción (CTA) --- */
@@ -87,7 +102,7 @@ body {
87
102
  }
88
103
 
89
104
  .features-section {
90
- padding: 5rem 0;
105
+ padding: 2rem 1rem;
91
106
  background-color: var(--website-light-bg);
92
107
  }
93
108
  .feature-item {
@@ -144,6 +159,12 @@ body {
144
159
  margin-bottom: 1.5rem;
145
160
  }
146
161
 
162
+ .article-footer {
163
+ margin-top: 4rem;
164
+ padding-top: 2rem;
165
+ border-top: 1px solid #e9ecef; /* Separador visual */
166
+ }
167
+
147
168
  /* --- Sección del Autor --- */
148
169
  .author-section {
149
170
  background-color: #fff;
@@ -170,6 +191,61 @@ body {
170
191
  .author-linkedin:hover {
171
192
  opacity: 0.8;
172
193
  }
194
+ .author-box {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 25px;
198
+ background-color: #f8f9fa;
199
+ padding: 25px;
200
+ border-radius: 12px;
201
+ }
202
+
203
+ .author-avatar img {
204
+ width: 90px;
205
+ height: 90px;
206
+ border-radius: 50%;
207
+ object-fit: cover;
208
+ border: 3px solid #fff;
209
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
210
+ }
211
+
212
+ .author-details {
213
+ line-height: 1.4; /* Reduce el espacio entre líneas */
214
+ }
215
+
216
+ .author-details p {
217
+ margin: 0; /* Elimina los márgenes por defecto de los párrafos */
218
+ }
219
+
220
+ .author-name {
221
+ font-weight: 600;
222
+ font-size: 1.25rem;
223
+ color: #212529;
224
+ }
225
+
226
+ .author-title {
227
+ font-size: 1rem;
228
+ color: #6c757d;
229
+ margin-top: 2px; /* Pequeño espacio después del nombre */
230
+ font-style: normal;
231
+ }
232
+
233
+ .author-social-link {
234
+ display: inline-block;
235
+ margin-top: 8px; /* Espacio reducido antes del enlace */
236
+ font-size: 0.95rem;
237
+ color: #007bff;
238
+ text-decoration: none;
239
+ font-weight: 500;
240
+ }
241
+
242
+ .author-social-link:hover {
243
+ text-decoration: underline;
244
+ }
245
+
246
+ .author-social-link i {
247
+ margin-right: 5px;
248
+ }
173
249
 
174
250
  /* --- Footer --- */
175
251
  .landing-footer {
@@ -179,4 +255,144 @@ body {
179
255
  text-align: center;
180
256
  margin-top: 4rem;
181
257
  border-top: 1px solid #e9ecef;
258
+ }
259
+
260
+
261
+ /* Language Switcher within header */
262
+ .language-switcher {
263
+ display: flex;
264
+ align-items: center;
265
+ font-size: 0.9rem;
266
+ /* margen izquierdo auto no es necesario si usas justify-content-between,
267
+ pero no molesta si el header cambia en el futuro */
268
+ margin-left: auto;
269
+ }
270
+
271
+ /* Links de idioma sobre fondo azul */
272
+ .language-link {
273
+ text-decoration: none;
274
+ color: #ffffff; /* texto blanco */
275
+ padding: 2px 8px;
276
+ font-weight: 500;
277
+ border-radius: 4px;
278
+ transition: background-color 0.2s ease;
279
+ }
280
+
281
+ /* Separador | puede heredar color blanco */
282
+ .language-switcher .mx-1 {
283
+ color: #ffffff;
284
+ }
285
+
286
+ /* Hover: blanco translúcido encima del azul */
287
+ .language-link:hover {
288
+ background-color: rgba(255, 255, 255, 0.25);
289
+ }
290
+
291
+ /* Idioma activo: un poco más marcado */
292
+ .language-link.active {
293
+ background-color: rgba(255, 255, 255, 0.35);
294
+ font-weight: 700;
295
+ color: #ffffff;
296
+ }
297
+
298
+ .btn-outline-website {
299
+ border: 2px solid var(--website-primary-color);
300
+ color: var(--website-primary-color);
301
+ background-color: transparent;
302
+ font-weight: 600;
303
+ transition: 0.2s ease-in-out;
304
+ }
305
+
306
+ .btn-outline-website:hover {
307
+ background-color: var(--website-primary-color);
308
+ color: #fff;
309
+ }
310
+
311
+ /* ------- Editions Section (Community vs Enterprise) ------- */
312
+ .edition-box {
313
+ background: #ffffff;
314
+ border: 1px solid rgba(0,0,0,0.08);
315
+ border-radius: 12px;
316
+ padding: 2rem;
317
+ text-align: left;
318
+ box-shadow: 0 4px 12px rgba(0,0,0,0.04);
319
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
320
+ }
321
+
322
+ .edition-box h3 {
323
+ font-weight: 600;
324
+ font-size: 1.4rem;
325
+ margin-bottom: 0.5rem;
326
+ color: var(--website-primary-color);
327
+ }
328
+
329
+ .edition-box p {
330
+ margin-bottom: 1.2rem;
331
+ color: var(--website-muted-text);
332
+ }
333
+
334
+ .edition-box ul {
335
+ padding-left: 0;
336
+ list-style: none;
337
+ margin: 0;
338
+ }
339
+
340
+ .edition-box ul li {
341
+ margin-bottom: 0.4rem;
342
+ font-size: 1rem;
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 0.5rem;
346
+ }
347
+
348
+ .edition-box ul li::before {
349
+ content: "✔";
350
+ font-weight: bold;
351
+ color: var(--website-primary-color);
352
+ font-size: 1rem;
353
+ }
354
+
355
+ /* Hover effect (solo desktop) */
356
+ @media (hover: hover) {
357
+ .edition-box:hover {
358
+ transform: translateY(-6px);
359
+ box-shadow: 0 8px 20px rgba(0,0,0,0.08);
360
+ }
361
+ }
362
+
363
+ /* Enterprise highlighting */
364
+ .edition-box.enterprise {
365
+ background: var(--website-light-bg);
366
+ border: 1px solid rgba(0,0,0,0.12);
367
+ }
368
+
369
+ /* Section title & spacing */
370
+ .editions-section h2 {
371
+ color: var(--website-dark-text);
372
+ font-weight: 700;
373
+ }
374
+
375
+ .editions-section .edition-box {
376
+ min-height: 100%;
377
+ }
378
+
379
+ .trusted-subtitle {
380
+ font-size: 0.9rem;
381
+ font-weight: 500;
382
+ color: var(--website-primary-color-dark);
383
+ opacity: 0.8;
384
+ text-align: center;
385
+ margin-top: -0.5rem;
386
+ margin-bottom: 1.2rem;
387
+ }
388
+
389
+ /* Estilo del link dentro del badge */
390
+ .trusted-link {
391
+ color: var(--website-primary-color-dark);
392
+ text-decoration: none;
393
+ font-weight: 600;
394
+ }
395
+
396
+ .trusted-link:hover {
397
+ text-decoration: underline;
182
398
  }
File without changes
@@ -1,7 +1,8 @@
1
1
  Eres un asistente que responde preguntas o ejecuta tareas según el contexto de la empresa.
2
2
 
3
3
  ### **Nombre de la empresa**
4
- ## Nombre: {{company}}, tambien se conoce como {{ company_short_name }}
4
+ ## Nombre: {{company}}, tambien se conoce como **{{ company_short_name }}**
5
+ ## Idioma: el idioma en que debes responder inicialmente es: {{language}}
5
6
 
6
7
  ### ** Información del usuario que esta consultando este chat**
7
8
  - Identificador unico de usuario: {{ user_identifier }}
@@ -4,6 +4,7 @@
4
4
  - Muchas columnas contienen información en formato **jsonb**.
5
5
  - Todas las consultas deben ser **sintácticamente correctas** y compatibles con PostgreSQL.
6
6
 
7
+ ## 🔍 Uso correcto de campos JSONB
7
8
  ## 🔍 Uso correcto de campos JSONB
8
9
 
9
10
  ### Regla central: ¡NO ASUMAS QUE LAS CLAVES JSONB SON COLUMNAS!
@@ -108,32 +109,66 @@ Ejemplo incorrecto: SUM(value->>'monto')::NUMERIC
108
109
  - Usa `TO_DATE(...)` para convertir el string a fecha:
109
110
  ```sql
110
111
  TO_DATE(jsonb_credit->>'created', 'DD-MM-YYYY')
111
- ´´´
112
+ ```
112
113
 
113
114
  - antes de comparar con `CURRENT_DATE` un campo de tipo jsonb,
114
115
  **siempre** castea explícitamente
115
116
 
116
- - No utilices TO_DATE si la columna ya es de tipo DATE o TIMESTAMP.
117
- Úsala solo si el campo es tipo TEXT o VARCHAR, y asegúrate de castearlo explícitamente si es necesario.
118
- TO_DATE espera un string, no una fecha ya en formato DATE, y causará un error de tipo de datos.
119
-
120
117
  - Si comparas fechas, asegúrate que ambos lados sean del mismo tipo.
121
118
  ejemplo:
122
119
  ```sql
123
- -- Si 'init_date' es DATE:
124
- TO_CHAR(init_date, 'DD-MM-YYYY') = ...
125
- -- Si 'created' es texto en formato 'DD-MM-YYYY':
126
- TO_DATE(jsonb_guarantee->>'created', 'DD-MM-YYYY')
120
+ -- Si 'buyer__date' es DATE:
121
+ TO_CHAR('buyer__date' , 'DD-MM-YYYY')
122
+ -- Si 'created_date' es texto en formato 'DD-MM-YYYY':
123
+ TO_DATE(jsonb_guarantee->>'created_date', 'DD-MM-YYYY')
127
124
  - Evitar comparar campos de tipo DATE o TIMESTAMP con strings generados por TO_CHAR.
128
125
  Usar funciones como date_trunc() o INTERVAL directamente.
129
126
 
127
+ 🎯 Regla para fechas que pueden venir vacías (solo TEXT / JSONB)
128
+
129
+ - Usa `TO_DATE()` ÚNICAMENTE cuando el valor de fecha sea un string:
130
+ - campos de tipo TEXT o VARCHAR
131
+ - o valores extraídos de JSONB con `->>` (por ejemplo `jsonb_credit->>'created'`).
132
+
133
+ - Si el campo de fecha es TEXTO y puede venir vacío (`''`), utiliza este patrón:
134
+ TO_DATE(NULLIF(campo_texto, ''), 'DD-MM-YYYY')
135
+
136
+ donde `campo_texto` debe ser SIEMPRE:
137
+ - un TEXT/VARCHAR, o
138
+ - una expresión de JSONB como `jsonb_column->>'field'`.
139
+
140
+ - **NUNCA** uses este patrón sobre columnas que ya son de tipo DATE o TIMESTAMP.
141
+ Ejemplos prohibidos:
142
+ - TO_DATE(NULLIF(bc.init_date, ''), 'DD-MM-YYYY')
143
+ - TO_DATE(NULLIF(c.init_date, ''), 'DD-MM-YYYY')
144
+ - TO_DATE(bc.init_date, 'DD-MM-YYYY')
145
+ - TO_DATE(c.init_date, 'DD-MM-YYYY')
146
+
147
+ - Para columnas DATE del esquema BCU (como ejemplo):
148
+ - `bcu_certificate.init_date`
149
+ - `bcu_certificate.end_date`
150
+ - `bcu_tender.adjudication_date`
151
+ - `bcu_tender.closing_date`
152
+
153
+ **Nunca** las envuelvas en `TO_DATE()` ni en `NULLIF(columna, '')`.
154
+ Deben usarse directamente:
155
+ - ORDER BY bc.init_date DESC NULLS LAST
156
+ - ORDER BY t.adjudication_date DESC NULLS LAST
157
+
130
158
  - Cuando necesites construir fechas a partir de valores numéricos
131
159
  (por ejemplo, año y mes), utiliza TO_DATE() con un string en formato 'YYYY-MM-DD',
132
160
  no uses concatenación sobre literales de tipo DATE.
133
161
 
134
- - Si el campo de fecha puede venir vacío (''), usa:
135
- TO_DATE(NULLIF(campo, ''), 'DD-MM-YYYY')
136
- Esto evita errores de conversión cuando la fecha está ausente o mal registrada.
162
+ - No utilices TO_DATE si la columna ya es de tipo DATE o TIMESTAMP.
163
+ Úsala solo si el campo es tipo TEXT o VARCHAR (por ejemplo, campos que vienen de JSONB con `->>`),
164
+ y siempre recuerda que TO_DATE espera un string.
165
+
166
+ - Si el campo de fecha **es texto** y puede venir vacío (`''`), usa:
167
+ TO_DATE(NULLIF(campo_texto, ''), 'DD-MM-YYYY')
168
+ Esto solo aplica cuando `campo_texto` es `TEXT`/`VARCHAR` o viene de `jsonb->>'campo'`.
169
+ **Nunca** uses este patrón sobre columnas que ya son DATE o TIMESTAMP
170
+ (en esos casos simplemente ordena o filtra con la columna tal cual, usando
171
+ `ORDER BY columna_fecha DESC NULLS LAST`).
137
172
 
138
173
  ### operaciones sobre campos de tipo JSONB
139
174
  - Cuando uses SUM o alguna otra función agregada sobre un valor extraído
@@ -2,7 +2,7 @@
2
2
  <div class="custom-company-header container d-flex justify-content-between align-items-center">
3
3
 
4
4
  {% if company_short_name and branding %}
5
- <a href="{{ url_for('home', company_short_name=company_short_name) }}"
5
+ <a href="{{ url_for('home', company_short_name=company_short_name, lang=request.args.get('lang', 'en')) }}"
6
6
  class="brand-name"
7
7
  style="{{ branding.primary_text_style }}">
8
8
  {{ branding.name }} IA
@@ -13,8 +13,33 @@
13
13
  </span>
14
14
  {% endif %}
15
15
 
16
- {# Texto "Powered by" con enlace a iatoolkit.com #}
17
- <span class="powered-by">
18
- Powered by <a href="{{ url_for('index') }}" rel="noopener noreferrer" class="iatoolkit-link">IAToolkit</a>
19
- </span>
16
+ {# Contenedor derecho: powered-by a la izquierda y selector de idioma a la derecha #}
17
+ <div class="d-flex align-items-center ms-auto">
18
+ <div class="me-3 d-flex align-items-center">
19
+ <span class="powered-by">
20
+ Powered by <a href="http://www.iatoolkit.com" rel="noopener noreferrer" class="iatoolkit-link">IAToolkit</a>
21
+ </span>
22
+ </div>
23
+
24
+ {% set current_lang = request.args.get('lang', 'en') %}
25
+ {% set has_endpoint = request.endpoint is not none %}
26
+
27
+ {% if has_endpoint %}
28
+ {% if request.view_args %}
29
+ {% set href_en = url_for(request.endpoint, lang='en', **request.view_args) %}
30
+ {% set href_es = url_for(request.endpoint, lang='es', **request.view_args) %}
31
+ {% else %}
32
+ {% set href_en = url_for(request.endpoint, lang='en') %}
33
+ {% set href_es = url_for(request.endpoint, lang='es') %}
34
+ {% endif %}
35
+ {% endif %}
36
+
37
+ <div class="language-switcher d-flex align-items-center">
38
+ <a href="{{ href_en }}" class="language-link {{ 'active' if current_lang == 'en' else '' }}">EN</a>
39
+ <span class="mx-1">|</span>
40
+ <a href="{{ href_es }}" class="language-link {{ 'active' if current_lang == 'es' else '' }}">ES</a>
41
+ </div>
42
+ </div>
43
+
44
+
20
45
  </div>
@@ -8,7 +8,7 @@
8
8
 
9
9
  <!-- 2. Formulario de Inicio de Sesión -->
10
10
  <form id="login-form"
11
- action="{{ url_for('login', company_short_name=company_short_name) }}"
11
+ action="{{ url_for('login', company_short_name=company_short_name, lang=request.args.get('lang', 'en')) }}"
12
12
  method="post">
13
13
  <div class="mb-3">
14
14
  <label for="email" class="form-label d-block">{{ t('ui.signup.email_label') }}</label>
@@ -28,14 +28,14 @@
28
28
  <!-- 3. Nueva Sección de Registro más Atractiva -->
29
29
  <div class="mt-4 pt-3 text-center" style="border-top: 1px solid #e0e0e0;">
30
30
  <span class="text-muted small">{{ t('ui.login_widget.no_account_prompt') }}</span>
31
- <a href="{{ url_for('signup', company_short_name=company_short_name) }}" id="signup-link"
31
+ <a href="{{ url_for('signup', company_short_name=company_short_name, lang=request.args.get('lang', 'en')) }}" id="signup-link"
32
32
  class="fw-bold ms-1 text-decoration-none" style="color: var(--brand-primary-color);">
33
33
  {{ t('ui.login_widget.signup_link') }}</a>
34
34
  </div>
35
35
 
36
36
  <!-- 4. Enlace de Recuperación de Contraseña (más sutil) -->
37
37
  <div class="text-center mt-2">
38
- <a href="{{ url_for('forgot_password', company_short_name=company_short_name) }}" class="text-decoration-none text-muted" style="font-size: 0.8rem;">
38
+ <a href="{{ url_for('forgot_password', company_short_name=company_short_name, lang=request.args.get('lang', 'en')) }}" class="text-decoration-none text-muted" style="font-size: 0.8rem;">
39
39
  {{ t('ui.login_widget.forgot_password_link') }}
40
40
  </a>
41
41
  </div>
@@ -25,7 +25,7 @@
25
25
  {{ branding.name }}
26
26
  </span>
27
27
  <span class="ms-2" data-bs-toggle="tooltip" data-bs-placement="bottom"
28
- title="Powered by IAToolkit ({{ iatoolkit_version }})">
28
+ title="Powered by IAToolkit {{ license }} ver. {{ iatoolkit_version }}">
29
29
  <i class="bi bi-info-circle" style="color: {{ branding.header_text_color }}; opacity: 0.7; font-size: 0.9rem;"></i>
30
30
  </span>
31
31
  </div>
@@ -24,7 +24,8 @@
24
24
  {{ t('ui.forgot_password.subtitle') }}
25
25
  </p>
26
26
 
27
- <form action="{{ url_for('forgot_password', company_short_name=company_short_name) }}" method="post">
27
+ <form action="{{ url_for('forgot_password', company_short_name=company_short_name, lang=lang) }}" method="post">
28
+ <input type="hidden" name="lang" value="{{ lang }}">
28
29
  <div class="mb-3">
29
30
  <label for="email" class="form-label text-secondary">{{ t('ui.signup.email_label') }}</label>
30
31
  <input type="email" id="email" name="email"
@@ -36,7 +37,7 @@
36
37
  </form>
37
38
 
38
39
  <div class="text-center mt-4 pt-3" style="border-top: 1px solid #e0e0e0;">
39
- <a href="{{ url_for('home', company_short_name=company_short_name) }}" class="text-muted text-decoration-none fw-semibold">
40
+ <a href="{{ url_for('home', company_short_name=company_short_name, lang=request.args.get('lang', 'en')) }}" class="text-muted text-decoration-none fw-semibold">
40
41
  <i class="bi bi-arrow-left me-1"></i>{{ t('ui.forgot_password.back_to_login') }}
41
42
  </a>
42
43
  </div>
@@ -60,7 +60,7 @@
60
60
 
61
61
  <div id="loading-status" class="ob-loading-band">
62
62
  <div class="spinner"></div>
63
- <p>Inicializando el contexto de {{ branding.name }} para la IA...</p>
63
+ <p>{{ t('ui.chat.init_context') }} </p>
64
64
  </div>
65
65
  </div>
66
66
  </div>
@@ -59,6 +59,9 @@
59
59
  <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
60
60
  </div>
61
61
 
62
+ <!-- language: html -->
63
+ <input type="hidden" name="lang" value="{{ lang or 'en' }}">
64
+
62
65
  <!-- Botón actualizado con la clase de branding -->
63
66
  <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.signup.signup_button') }}</button>
64
67
  </form>
@@ -12,7 +12,7 @@ from iatoolkit.services.auth_service import AuthService
12
12
  from iatoolkit.services.query_service import QueryService
13
13
  from iatoolkit.services.branding_service import BrandingService
14
14
  from iatoolkit.services.configuration_service import ConfigurationService
15
- from iatoolkit.services.prompt_manager_service import PromptService
15
+ from iatoolkit.services.prompt_service import PromptService
16
16
  from iatoolkit.services.i18n_service import I18nService
17
17
  from iatoolkit.common.util import Utility
18
18
  from iatoolkit.services.jwt_service import JWTService
@@ -24,7 +24,7 @@ class ChangePasswordView(MethodView):
24
24
  self.branding_service = branding_service
25
25
  self.i18n_service = i18n_service
26
26
 
27
- self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
27
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
28
28
  self.bcrypt = Bcrypt()
29
29
 
30
30
  def get(self, company_short_name: str, token: str):
@@ -21,7 +21,7 @@ class ForgotPasswordView(MethodView):
21
21
  self.branding_service = branding_service
22
22
  self.i18n_service = i18n_service
23
23
 
24
- self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
24
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
25
25
 
26
26
  def get(self, company_short_name: str):
27
27
  # get company info
@@ -31,9 +31,11 @@ class ForgotPasswordView(MethodView):
31
31
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
32
32
 
33
33
  branding_data = self.branding_service.get_company_branding(company_short_name)
34
+ current_lang = request.args.get("lang", "en")
34
35
  return render_template('forgot_password.html',
35
36
  company_short_name=company_short_name,
36
- branding=branding_data
37
+ branding=branding_data,
38
+ lang=current_lang
37
39
  )
38
40
 
39
41
  def post(self, company_short_name: str):
@@ -53,7 +55,9 @@ class ForgotPasswordView(MethodView):
53
55
  company_short_name=company_short_name,
54
56
  token=token, _external=True)
55
57
 
56
- response = self.profile_service.forgot_password(email=email, reset_url=reset_url)
58
+ response = self.profile_service.forgot_password(
59
+ company_short_name=company_short_name,
60
+ email=email, reset_url=reset_url)
57
61
  if "error" in response:
58
62
  flash(response["error"], 'error')
59
63
  return render_template(
@@ -63,7 +67,8 @@ class ForgotPasswordView(MethodView):
63
67
  form_data={"email": email}), 400
64
68
 
65
69
  flash(self.i18n_service.t('flash_messages.forgot_password_success'), 'success')
66
- return redirect(url_for('home', company_short_name=company_short_name))
70
+ lang = request.args.get("lang", "en")
71
+ return redirect(url_for('home', company_short_name=company_short_name, lang=lang))
67
72
 
68
73
  except Exception as e:
69
74
  flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
@@ -5,7 +5,7 @@
5
5
 
6
6
  from flask import request, jsonify
7
7
  from flask.views import MethodView
8
- from iatoolkit.services.history_service import HistoryService
8
+ from iatoolkit.services.history_manager_service import HistoryManagerService
9
9
  from iatoolkit.services.auth_service import AuthService
10
10
  from iatoolkit.services.i18n_service import I18nService
11
11
  from injector import inject
@@ -21,7 +21,7 @@ class HistoryApiView(MethodView):
21
21
  @inject
22
22
  def __init__(self,
23
23
  auth_service: AuthService,
24
- history_service: HistoryService,
24
+ history_service: HistoryManagerService,
25
25
  i18n_service: I18nService):
26
26
  self.auth_service = auth_service
27
27
  self.history_service = history_service
@@ -39,7 +39,7 @@ class HistoryApiView(MethodView):
39
39
 
40
40
  # 2. Call the history service with the unified identifier.
41
41
  # The service's signature should now only expect user_identifier.
42
- response = self.history_service.get_history(
42
+ response = self.history_service.get_full_history(
43
43
  company_short_name=company_short_name,
44
44
  user_identifier=user_identifier
45
45
  )
@@ -1,5 +1,5 @@
1
1
  # iatoolkit/views/home_view.py
2
- from flask import render_template, render_template_string
2
+ from flask import render_template, render_template_string, request
3
3
  from flask.views import MethodView
4
4
  from injector import inject
5
5
  from iatoolkit.services.profile_service import ProfileService
@@ -33,7 +33,9 @@ class HomeView(MethodView):
33
33
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
34
 
35
35
  branding_data = self.branding_service.get_company_branding(company_short_name)
36
- home_template = self.util.get_company_template(company_short_name, "home.html")
36
+
37
+ template_name = self.util.get_template_by_language("home")
38
+ home_template = self.util.get_company_template(company_short_name, template_name)
37
39
 
38
40
  # 2. Verificamos si el archivo de plantilla personalizado no existe.
39
41
  if not home_template:
@@ -54,7 +54,7 @@ class InitContextApiView(MethodView):
54
54
  response_message = {'status': 'OK', 'message': success_message}
55
55
 
56
56
  # if received a response ID with the context, return it
57
- if response.get('response_id'):
57
+ if response and response.get('response_id'):
58
58
  response_message['response_id'] = response['response_id']
59
59
 
60
60
  return jsonify(response_message), 200
@@ -41,18 +41,19 @@ class LLMQueryApiView(MethodView):
41
41
  result = self.query_service.llm_query(
42
42
  company_short_name=company_short_name,
43
43
  user_identifier=user_identifier,
44
+ model=data.get('model', ''),
44
45
  question=data.get('question', ''),
45
46
  prompt_name=data.get('prompt_name'),
46
47
  client_data=data.get('client_data', {}),
47
- response_id = data.get('response_id'),
48
+ ignore_history=data.get('ignore_history', False),
48
49
  files=data.get('files', [])
49
50
  )
50
51
  if 'error' in result:
51
- return jsonify(result), 407
52
+ return jsonify(result), 409
52
53
 
53
54
  return jsonify(result), 200
54
55
 
55
56
  except Exception as e:
56
57
  logging.exception(
57
58
  f"Unexpected error: {e}")
58
- return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
59
+ return jsonify({"error": True, "error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
@@ -12,7 +12,7 @@ from injector import inject
12
12
  import base64
13
13
 
14
14
 
15
- class FileStoreApiView(MethodView):
15
+ class LoadDocumentApiView(MethodView):
16
16
  @inject
17
17
  def __init__(self,
18
18
  auth_service: AuthService,