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.
- iatoolkit/__init__.py +15 -5
- iatoolkit/base_company.py +4 -58
- iatoolkit/cli_commands.py +6 -7
- iatoolkit/common/exceptions.py +1 -0
- iatoolkit/common/routes.py +12 -28
- iatoolkit/common/util.py +7 -1
- iatoolkit/company_registry.py +50 -14
- iatoolkit/{iatoolkit.py → core.py} +54 -55
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- iatoolkit/infra/llm_client.py +9 -5
- iatoolkit/locales/en.yaml +10 -2
- iatoolkit/locales/es.yaml +171 -162
- iatoolkit/repositories/database_manager.py +59 -14
- iatoolkit/repositories/llm_query_repo.py +34 -22
- iatoolkit/repositories/models.py +16 -18
- iatoolkit/repositories/profile_repo.py +5 -10
- iatoolkit/repositories/vs_repo.py +9 -4
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +19 -11
- iatoolkit/services/configuration_service.py +219 -46
- iatoolkit/services/dispatcher_service.py +31 -225
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +9 -6
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/history_manager_service.py +189 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +37 -32
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
- iatoolkit/services/query_service.py +192 -191
- iatoolkit/services/sql_service.py +63 -12
- iatoolkit/services/tool_service.py +231 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +18 -0
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +17 -5
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/styles/chat_iatoolkit.css +1 -1
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +2 -1
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/chat.html +1 -1
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -1
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +1 -1
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
- iatoolkit/views/login_view.py +17 -5
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-0.91.1.dist-info/METADATA +268 -0
- iatoolkit-0.91.1.dist-info/RECORD +125 -0
- iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit-0.71.4.dist-info/METADATA +0 -276
- iatoolkit-0.71.4.dist-info/RECORD +0 -122
- {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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:
|
|
65
|
-
gap:
|
|
66
|
-
margin-bottom:
|
|
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
|
-
|
|
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:
|
|
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 '
|
|
124
|
-
TO_CHAR(
|
|
125
|
-
-- Si '
|
|
126
|
-
TO_DATE(jsonb_guarantee->>'
|
|
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
|
-
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
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>
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -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
|
|
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>
|
iatoolkit/templates/signup.html
CHANGED
|
@@ -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.
|
|
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("
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
42
|
+
response = self.history_service.get_full_history(
|
|
43
43
|
company_short_name=company_short_name,
|
|
44
44
|
user_identifier=user_identifier
|
|
45
45
|
)
|
iatoolkit/views/home_view.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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),
|
|
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'
|
|
59
|
+
return jsonify({"error": True, "error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
|