iatoolkit 0.22.1__py3-none-any.whl → 0.50.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/common/routes.py +31 -31
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +0 -21
- iatoolkit/iatoolkit.py +5 -18
- iatoolkit/infra/llm_client.py +3 -5
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/repositories/models.py +1 -2
- iatoolkit/services/auth_service.py +74 -0
- iatoolkit/services/dispatcher_service.py +12 -21
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/history_service.py +2 -11
- iatoolkit/services/profile_service.py +83 -25
- iatoolkit/services/query_service.py +132 -82
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +3 -6
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/js/chat_feedback.js +1 -1
- iatoolkit/static/js/chat_history.js +1 -5
- iatoolkit/static/js/chat_main.js +1 -1
- iatoolkit/static/styles/landing_page.css +62 -2
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_login_widget.html +6 -8
- iatoolkit/templates/chat.html +78 -4
- iatoolkit/templates/error.html +1 -1
- iatoolkit/templates/index.html +38 -11
- iatoolkit/templates/{home.html → login_test.html} +11 -51
- iatoolkit/views/external_login_view.py +50 -111
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +4 -4
- iatoolkit/views/history_api_view.py +52 -0
- iatoolkit/views/init_context_api_view.py +62 -0
- iatoolkit/views/llmquery_api_view.py +50 -0
- iatoolkit/views/llmquery_web_view.py +38 -0
- iatoolkit/views/{home_view.py → login_test_view.py} +2 -5
- iatoolkit/views/login_view.py +79 -56
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +4 -4
- iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -19
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.2.dist-info}/METADATA +2 -2
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.2.dist-info}/RECORD +40 -41
- iatoolkit/common/auth.py +0 -200
- iatoolkit/templates/login.html +0 -43
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/init_context_view.py +0 -35
- iatoolkit/views/llmquery_view.py +0 -65
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.2.dist-info}/top_level.txt +0 -0
|
@@ -6,80 +6,138 @@
|
|
|
6
6
|
from iatoolkit.infra.redis_session_manager import RedisSessionManager
|
|
7
7
|
from typing import List, Dict, Optional
|
|
8
8
|
import json
|
|
9
|
+
import logging
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class UserSessionContextService:
|
|
12
13
|
"""
|
|
13
|
-
Gestiona el contexto de la sesión del usuario
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Usa RedisSessionManager para persistencia directa en Redis.
|
|
14
|
+
Gestiona el contexto de la sesión del usuario usando un único Hash de Redis por sesión.
|
|
15
|
+
Esto mejora la atomicidad y la eficiencia.
|
|
17
16
|
"""
|
|
18
17
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
if not user_identifier:
|
|
22
|
-
return None
|
|
23
|
-
return f"llm_history:{company_short_name}/{user_identifier}"
|
|
24
|
-
|
|
25
|
-
def _get_user_data_key(self, company_short_name: str, user_identifier: str) -> str:
|
|
18
|
+
def _get_session_key(self, company_short_name: str, user_identifier: str) -> Optional[str]:
|
|
19
|
+
"""Devuelve la clave única de Redis para el Hash de sesión del usuario."""
|
|
26
20
|
user_identifier = (user_identifier or "").strip()
|
|
27
|
-
if not user_identifier:
|
|
21
|
+
if not company_short_name or not user_identifier:
|
|
28
22
|
return None
|
|
29
|
-
return f"
|
|
23
|
+
return f"session:{company_short_name}/{user_identifier}"
|
|
30
24
|
|
|
31
25
|
def clear_all_context(self, company_short_name: str, user_identifier: str):
|
|
32
|
-
"""Limpia
|
|
33
|
-
self.
|
|
34
|
-
|
|
26
|
+
"""Limpia el contexto de sesión para un usuario de forma atómica."""
|
|
27
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
28
|
+
if session_key:
|
|
29
|
+
# RedisSessionManager.remove(session_key)
|
|
30
|
+
# 'profile_data' should not be deleted
|
|
31
|
+
RedisSessionManager.hdel(session_key, 'context_version')
|
|
32
|
+
RedisSessionManager.hdel(session_key, 'context_history')
|
|
33
|
+
RedisSessionManager.hdel(session_key, 'last_response_id')
|
|
35
34
|
|
|
36
35
|
def clear_llm_history(self, company_short_name: str, user_identifier: str):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
"""Limpia solo los campos relacionados con el historial del LLM (ID y chat)."""
|
|
37
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
38
|
+
if session_key:
|
|
39
|
+
RedisSessionManager.hdel(session_key, 'last_response_id', 'context_history')
|
|
40
|
+
|
|
41
|
+
def get_last_response_id(self, company_short_name: str, user_identifier: str) -> Optional[str]:
|
|
42
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
43
|
+
if not session_key:
|
|
44
44
|
return None
|
|
45
|
-
|
|
46
|
-
return RedisSessionManager.get(history_key, '')
|
|
45
|
+
return RedisSessionManager.hget(session_key, 'last_response_id')
|
|
47
46
|
|
|
48
47
|
def save_last_response_id(self, company_short_name: str, user_identifier: str, response_id: str):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
RedisSessionManager.set(history_key, response_id)
|
|
48
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
49
|
+
if session_key:
|
|
50
|
+
RedisSessionManager.hset(session_key, 'last_response_id', response_id)
|
|
55
51
|
|
|
56
52
|
def save_context_history(self, company_short_name: str, user_identifier: str, context_history: List[Dict]):
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
54
|
+
if session_key:
|
|
55
|
+
try:
|
|
56
|
+
history_json = json.dumps(context_history)
|
|
57
|
+
RedisSessionManager.hset(session_key, 'context_history', history_json)
|
|
58
|
+
except (TypeError, ValueError) as e:
|
|
59
|
+
logging.error(f"Error al serializar context_history para {session_key}: {e}")
|
|
61
60
|
|
|
62
61
|
def get_context_history(self, company_short_name: str, user_identifier: str) -> Optional[List[Dict]]:
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
63
|
+
if not session_key:
|
|
64
|
+
return None
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
history_json = RedisSessionManager.hget(session_key, 'context_history')
|
|
67
|
+
if not history_json:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
return json.loads(history_json)
|
|
72
|
+
except json.JSONDecodeError:
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
def save_profile_data(self, company_short_name: str, user_identifier: str, data: dict):
|
|
76
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
77
|
+
if session_key:
|
|
78
|
+
try:
|
|
79
|
+
data_json = json.dumps(data)
|
|
80
|
+
RedisSessionManager.hset(session_key, 'profile_data', data_json)
|
|
81
|
+
except (TypeError, ValueError) as e:
|
|
82
|
+
logging.error(f"Error al serializar profile_data para {session_key}: {e}")
|
|
83
|
+
|
|
84
|
+
def get_profile_data(self, company_short_name: str, user_identifier: str) -> dict:
|
|
85
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
86
|
+
if not session_key:
|
|
77
87
|
return {}
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
data_json = RedisSessionManager.hget(session_key, 'profile_data')
|
|
90
|
+
if not data_json:
|
|
91
|
+
return {}
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
try:
|
|
94
|
+
return json.loads(data_json)
|
|
95
|
+
except json.JSONDecodeError:
|
|
96
|
+
return {}
|
|
97
|
+
|
|
98
|
+
def save_context_version(self, company_short_name: str, user_identifier: str, version: str):
|
|
99
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
100
|
+
if session_key:
|
|
101
|
+
RedisSessionManager.hset(session_key, 'context_version', version)
|
|
102
|
+
|
|
103
|
+
def get_context_version(self, company_short_name: str, user_identifier: str) -> Optional[str]:
|
|
104
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
105
|
+
if not session_key:
|
|
106
|
+
return None
|
|
107
|
+
return RedisSessionManager.hget(session_key, 'context_version')
|
|
108
|
+
|
|
109
|
+
def save_prepared_context(self, company_short_name: str, user_identifier: str, context: str, version: str):
|
|
110
|
+
"""Guarda un contexto de sistema pre-renderizado y su versión, listos para ser enviados al LLM."""
|
|
111
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
112
|
+
if session_key:
|
|
113
|
+
RedisSessionManager.hset(session_key, 'prepared_context', context)
|
|
114
|
+
RedisSessionManager.hset(session_key, 'prepared_context_version', version)
|
|
115
|
+
|
|
116
|
+
def get_and_clear_prepared_context(self, company_short_name: str, user_identifier: str) -> tuple:
|
|
117
|
+
"""Obtiene el contexto preparado y su versión, y los elimina para asegurar que se usan una sola vez."""
|
|
118
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
119
|
+
if not session_key:
|
|
120
|
+
return None, None
|
|
121
|
+
|
|
122
|
+
pipe = RedisSessionManager.pipeline()
|
|
123
|
+
pipe.hget(session_key, 'prepared_context')
|
|
124
|
+
pipe.hget(session_key, 'prepared_context_version')
|
|
125
|
+
pipe.hdel(session_key, 'prepared_context', 'prepared_context_version')
|
|
126
|
+
results = pipe.execute()
|
|
127
|
+
|
|
128
|
+
# results[0] es el contexto, results[1] es la versión
|
|
129
|
+
return (results[0], results[1]) if results else (None, None)
|
|
130
|
+
|
|
131
|
+
# --- Métodos de Bloqueo ---
|
|
132
|
+
def acquire_lock(self, lock_key: str, expire_seconds: int) -> bool:
|
|
133
|
+
"""Intenta adquirir un lock. Devuelve True si se adquiere, False si no."""
|
|
134
|
+
# SET con NX (solo si no existe) y EX (expiración) es una operación atómica.
|
|
135
|
+
return RedisSessionManager.set(lock_key, "1", ex=expire_seconds, nx=True)
|
|
136
|
+
|
|
137
|
+
def release_lock(self, lock_key: str):
|
|
138
|
+
"""Libera un lock."""
|
|
139
|
+
RedisSessionManager.remove(lock_key)
|
|
140
|
+
|
|
141
|
+
def is_locked(self, lock_key: str) -> bool:
|
|
142
|
+
"""Verifica si un lock existe."""
|
|
143
|
+
return RedisSessionManager.exists(lock_key)
|
|
@@ -98,7 +98,7 @@ $(document).ready(function () {
|
|
|
98
98
|
const sendFeedback = async function(message) {
|
|
99
99
|
const activeStars = $('.star.active').length;
|
|
100
100
|
const data = {
|
|
101
|
-
"
|
|
101
|
+
"user_identifier": window.user_identifier,
|
|
102
102
|
"message": message,
|
|
103
103
|
"rating": activeStars,
|
|
104
104
|
"space": "spaces/AAQAupQldd4", // Este valor podría necesitar ser dinámico
|
|
@@ -40,11 +40,7 @@ $(document).ready(function () {
|
|
|
40
40
|
historyContent.hide();
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
44
|
-
external_user_id: window.externalUserId
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const responseData = await callLLMAPI("/history", data, "POST");
|
|
43
|
+
const responseData = await callLLMAPI("/api/history", {}, "POST");
|
|
48
44
|
|
|
49
45
|
if (responseData && responseData.history) {
|
|
50
46
|
// Guardar datos globalmente
|
iatoolkit/static/js/chat_main.js
CHANGED
|
@@ -173,7 +173,7 @@ const handleChatMessage = async function () {
|
|
|
173
173
|
prompt_name: promptName,
|
|
174
174
|
client_data: clientData,
|
|
175
175
|
files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
|
|
176
|
-
|
|
176
|
+
user_identifier: window.user_identifier
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
const responseData = await callLLMAPI("/llm_query", data, "POST");
|
|
@@ -49,8 +49,35 @@ body {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
.hero-title {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
font-size: 3rem;
|
|
53
|
+
font-weight: 700;
|
|
54
|
+
background: linear-gradient(90deg, #4F46E5, #818CF8);
|
|
55
|
+
-webkit-background-clip: text;
|
|
56
|
+
-webkit-text-fill-color: transparent;
|
|
57
|
+
line-height: 1.1;
|
|
58
|
+
letter-spacing: -0.5px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.hero-bullets {
|
|
62
|
+
list-style: none;
|
|
63
|
+
padding-left: 2rem;
|
|
64
|
+
margin-top: 1.5rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.hero-bullets li {
|
|
68
|
+
font-size: 1.15rem;
|
|
69
|
+
color: #4b5563;
|
|
70
|
+
margin-bottom: 0.8rem;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: flex-start;
|
|
73
|
+
gap: 0.6rem;
|
|
74
|
+
line-height: 1.5;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hero-bullets i {
|
|
78
|
+
color: #4F46E5;
|
|
79
|
+
font-size: 1.3rem;
|
|
80
|
+
margin-top: 0.2rem;
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
.value-proposition .hero-subtitle {
|
|
@@ -69,6 +96,39 @@ body {
|
|
|
69
96
|
color: var(--brand-primary-color, #0d6efd);
|
|
70
97
|
}
|
|
71
98
|
|
|
99
|
+
.author-section {
|
|
100
|
+
background: #ffffff;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.author-card {
|
|
104
|
+
background: #f8f9fa;
|
|
105
|
+
border: 1px solid #e9ecef;
|
|
106
|
+
border-radius: 10px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.author-avatar {
|
|
110
|
+
width: 64px;
|
|
111
|
+
height: 64px;
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
background: var(--brand-primary-color, #0d6efd);
|
|
114
|
+
color: var(--brand-text-on-primary, #fff);
|
|
115
|
+
display: flex; align-items: center; justify-content: center;
|
|
116
|
+
font-size: 2rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.author-bio {
|
|
120
|
+
color: #4b5563;
|
|
121
|
+
line-height: 1.6;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.author-linkedin {
|
|
125
|
+
color: var(--brand-primary-color, #0d6efd);
|
|
126
|
+
text-decoration: none;
|
|
127
|
+
}
|
|
128
|
+
.author-linkedin:hover {
|
|
129
|
+
text-decoration: underline;
|
|
130
|
+
}
|
|
131
|
+
|
|
72
132
|
/* --- Estilo para el texto introductorio del widget --- */
|
|
73
133
|
.widget-intro-text {
|
|
74
134
|
font-style: italic;
|
|
@@ -1,24 +1,15 @@
|
|
|
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
|
|
4
|
+
## Nombre: {{company}}, tambien se conoce como {{ company_short_name }}
|
|
5
5
|
|
|
6
6
|
### ** Información del usuario que esta consultando este chat**
|
|
7
|
-
|
|
7
|
+
- Identificador unico de usuario: {{ user_identifier }}
|
|
8
8
|
- Nombre: {{ user_fullname }}
|
|
9
9
|
- Email: {{ user_email }}
|
|
10
10
|
- Tipo de usuario: {% if user_is_local %}Interno{% else %}Externo{% endif %}
|
|
11
|
-
-
|
|
11
|
+
- Rol de usuario: {{ user_rol }}
|
|
12
12
|
|
|
13
|
-
{% if user_name %}
|
|
14
|
-
El usuario que consulta se identifica con la variable `user_name` y tiene el
|
|
15
|
-
siguiente valor: {{ user_name }}.
|
|
16
|
-
|
|
17
|
-
Este usuario tiene el rol: {{ user_rol }} en el producto {{ user_product }}.
|
|
18
|
-
|
|
19
|
-
{% else %}
|
|
20
|
-
El usuario que consulta se identifica como: {{ user_id }}
|
|
21
|
-
{% endif %}
|
|
22
13
|
|
|
23
14
|
## Servicios de datos (function calls) disponibles en {{company.name}}:
|
|
24
15
|
{% for service in service_list %}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<!-- templates/_login_widget.html -->
|
|
2
2
|
<div class="border rounded p-4 shadow-sm bg-light">
|
|
3
|
-
<!-- 1.
|
|
3
|
+
<!-- 1. Encabezado de Marketing -->
|
|
4
4
|
<div class="text-center mb-4">
|
|
5
|
-
<h4 class="form-title fw-bold">Acceso a la Plataforma</h4>
|
|
6
|
-
<!-- Párrafo modificado con nuevo texto y nueva clase CSS -->
|
|
7
5
|
<p class="text-muted widget-intro-text">
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Explora IAToolkit con una empresa de ejemplo.
|
|
7
|
+
Regístrate para vivir la experiencia completa de un usuario real que consulta a la plataforma.
|
|
10
8
|
</p>
|
|
11
9
|
</div>
|
|
12
10
|
|
|
@@ -15,16 +13,16 @@
|
|
|
15
13
|
action="{{ url_for('chat', company_short_name=company_short_name) }}"
|
|
16
14
|
method="post">
|
|
17
15
|
<div class="mb-3">
|
|
18
|
-
<label for="email" class="form-label d-block
|
|
16
|
+
<label for="email" class="form-label d-block">Correo Electrónico</label>
|
|
19
17
|
<input type="email" id="email" name="email" class="form-control" required>
|
|
20
18
|
</div>
|
|
21
19
|
<div class="mb-3">
|
|
22
|
-
<label for="password" class="form-label d-block
|
|
20
|
+
<label for="password" class="form-label d-block">Contraseña</label>
|
|
23
21
|
<input type="password" id="password" name="password"
|
|
24
22
|
class="form-control" required>
|
|
25
23
|
</div>
|
|
26
24
|
<button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2">
|
|
27
|
-
|
|
25
|
+
Probar demo ahora
|
|
28
26
|
</button>
|
|
29
27
|
</form>
|
|
30
28
|
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div class="d-flex align-items-center">
|
|
28
28
|
<!-- 1. ID de Usuario -->
|
|
29
29
|
<span style="{{ branding.secondary_text_style }}">
|
|
30
|
-
{{
|
|
30
|
+
{{ user_identifier }}
|
|
31
31
|
</span>
|
|
32
32
|
|
|
33
33
|
<!-- 2. Separador Vertical -->
|
|
@@ -38,13 +38,20 @@
|
|
|
38
38
|
class="action-icon-style" title="Historial con mis consultas" style="color: {{ branding.header_text_color }};">
|
|
39
39
|
<i class="bi bi-clock-history"></i>
|
|
40
40
|
</a>
|
|
41
|
+
<a href="javascript:void(0);"
|
|
42
|
+
id="force-reload-button"
|
|
43
|
+
class="ms-3 action-icon-style"
|
|
44
|
+
title="Forzar Recarga de Contexto"
|
|
45
|
+
style="color: {{ branding.header_text_color }};">
|
|
46
|
+
<i class="bi bi-arrow-clockwise"></i>
|
|
47
|
+
</a>
|
|
41
48
|
<a href="javascript:void(0);" id="send-feedback-button"
|
|
42
49
|
class="ms-3 action-icon-style" title="Tu feedback es muy importante" style="color: {{ branding.header_text_color }};">
|
|
43
50
|
<i class="bi bi-emoji-smile"></i>
|
|
44
51
|
</a>
|
|
45
52
|
|
|
46
53
|
<!-- Icono de cerrar sesión (al final) -->
|
|
47
|
-
{% if
|
|
54
|
+
{% if user_is_local %}
|
|
48
55
|
<a href="{{ url_for('logout', company_short_name=company_short_name, _external=True) }}"
|
|
49
56
|
class="ms-3 action-icon-style" title="Cerrar sesión" style="color: {{ branding.header_text_color }} !important;">
|
|
50
57
|
<i class="bi bi-box-arrow-right"></i>
|
|
@@ -163,8 +170,8 @@
|
|
|
163
170
|
<script>
|
|
164
171
|
// --- Global Configuration from Backend ---
|
|
165
172
|
window.companyShortName = "{{ company_short_name }}";
|
|
173
|
+
window.user_identifier = "{{ user_identifier }}";
|
|
166
174
|
window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
|
|
167
|
-
window.externalUserId = "{{ external_user_id }}";
|
|
168
175
|
window.availablePrompts = {{ prompts.message | tojson }};
|
|
169
176
|
window.sendButtonColor = "{{ branding.send_button_color }}";
|
|
170
177
|
|
|
@@ -180,8 +187,13 @@
|
|
|
180
187
|
<script src="{{ url_for('static', filename='js/chat_feedback.js', _external=True) }}"></script>
|
|
181
188
|
<script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
|
|
182
189
|
|
|
190
|
+
|
|
191
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
|
|
192
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
|
193
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
|
|
194
|
+
|
|
183
195
|
<script>
|
|
184
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
196
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
185
197
|
const promptCollapse = document.getElementById('prompt-assistant-collapse');
|
|
186
198
|
if (promptCollapse) {
|
|
187
199
|
promptCollapse.addEventListener('shown.bs.collapse', function () {
|
|
@@ -193,6 +205,7 @@
|
|
|
193
205
|
});
|
|
194
206
|
}
|
|
195
207
|
});
|
|
208
|
+
|
|
196
209
|
document.addEventListener('DOMContentLoaded', function () {
|
|
197
210
|
// Inicializar todos los tooltips de la página
|
|
198
211
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
|
@@ -200,5 +213,66 @@
|
|
|
200
213
|
return new bootstrap.Tooltip(tooltipTriggerEl)
|
|
201
214
|
})
|
|
202
215
|
});
|
|
216
|
+
|
|
217
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
218
|
+
const reloadButton = document.getElementById('force-reload-button');
|
|
219
|
+
if (!reloadButton) return;
|
|
220
|
+
|
|
221
|
+
const originalIconClass = 'bi bi-arrow-clockwise';
|
|
222
|
+
const spinnerIconClass = 'spinner-border spinner-border-sm';
|
|
223
|
+
|
|
224
|
+
// Configuración de Toastr para que aparezca abajo a la derecha
|
|
225
|
+
toastr.options = { "positionClass": "toast-bottom-right", "preventDuplicates": true };
|
|
226
|
+
|
|
227
|
+
reloadButton.addEventListener('click', function(event) {
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
|
|
230
|
+
if (reloadButton.disabled) return; // Prevenir doble clic
|
|
231
|
+
|
|
232
|
+
// 1. Deshabilitar y mostrar spinner
|
|
233
|
+
reloadButton.disabled = true;
|
|
234
|
+
const icon = reloadButton.querySelector('i');
|
|
235
|
+
icon.className = spinnerIconClass;
|
|
236
|
+
toastr.info('Iniciando recarga de contexto en segundo plano...');
|
|
237
|
+
|
|
238
|
+
// 2. Construir la URL dinámicamente
|
|
239
|
+
const company = window.companyShortName;
|
|
240
|
+
const reloadUrl = `/${company}/api/init_context_api`;
|
|
241
|
+
|
|
242
|
+
// 3. Hacer la llamada AJAX con POST
|
|
243
|
+
fetch(reloadUrl, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json'
|
|
247
|
+
},
|
|
248
|
+
// Envía un cuerpo vacío o los datos necesarios
|
|
249
|
+
body: JSON.stringify({})
|
|
250
|
+
})
|
|
251
|
+
.then(response => {
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
return response.json().then(err => {
|
|
254
|
+
throw new Error(err.error_message || `Error del servidor: ${response.status}`);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return response.json();
|
|
258
|
+
})
|
|
259
|
+
.then(data => {
|
|
260
|
+
if (data.status === 'OK') {
|
|
261
|
+
toastr.success(data.message || 'Contexto recargado exitosamente.');
|
|
262
|
+
} else {
|
|
263
|
+
toastr.error(data.error_message || 'Ocurrió un error desconocido.');
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
.catch(error => {
|
|
267
|
+
console.error('Error durante la recarga del contexto:', error);
|
|
268
|
+
toastr.error(error.message || 'Error de red al intentar recargar.');
|
|
269
|
+
})
|
|
270
|
+
.finally(() => {
|
|
271
|
+
// 4. Restaurar el botón
|
|
272
|
+
reloadButton.disabled = false;
|
|
273
|
+
icon.className = originalIconClass;
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
203
277
|
</script>
|
|
204
278
|
{% endblock %}
|
iatoolkit/templates/error.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<p class="mt-3 text-muted">
|
|
10
10
|
{{ message }}
|
|
11
11
|
</p>
|
|
12
|
-
<a href="{{ url_for('
|
|
12
|
+
<a href="{{ url_for('index', company_short_name=company_short_name) }}" class="btn btn-primary mt-4">Volver al Inicio</a>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
{% endblock %}
|
iatoolkit/templates/index.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{% extends "base.html" %}
|
|
2
2
|
|
|
3
|
-
{% block title %}IAToolkit - Acelerador de IA
|
|
3
|
+
{% block title %}IAToolkit - Acelerador de IA {% endblock %}
|
|
4
4
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
|
|
@@ -23,17 +23,14 @@
|
|
|
23
23
|
<div class="row align-items-center">
|
|
24
24
|
<div class="col-lg-7">
|
|
25
25
|
<div class="value-proposition">
|
|
26
|
-
<h1 class="hero-title gradient-text">
|
|
27
|
-
|
|
28
|
-
datos internos —bases de datos SQL, API's y documentos— con un un asistente de IA
|
|
29
|
-
que entiende tu negocio desde el primer día.</p>
|
|
30
|
-
<p class="hero-subtitle">
|
|
31
|
-
Es un producto para ingenieros de software que quieren construir aplicaciones de IA
|
|
32
|
-
a gran velocidad. Provee la infraestructura fundamental
|
|
33
|
-
para que tú te puedas enfocar en conectar tus datos y crear tus prompts.</p>
|
|
34
|
-
<p class="hero-subtitle">
|
|
35
|
-
Te invito a descargarlo y probar el "hello, world" de IAToolkit.</p>
|
|
26
|
+
<h1 class="hero-title gradient-text">IA que entiende tu <span class="text-dark">negocio</span> desde el primer día.</h1>
|
|
27
|
+
|
|
36
28
|
</div>
|
|
29
|
+
<ul class="hero-bullets">
|
|
30
|
+
<li><i class="bi bi-database"></i> Conecta tus bases de datos, APIs y documentos.</li>
|
|
31
|
+
<li><i class="bi bi-chat-dots"></i> Construye un asistente que entiende el lenguaje de tu empresa.</li>
|
|
32
|
+
<li><i class="bi bi-rocket-takeoff"></i> Despliega donde quieras, con tu marca y tus reglas.</li>
|
|
33
|
+
</ul>
|
|
37
34
|
</div>
|
|
38
35
|
<div class="col-lg-5">
|
|
39
36
|
{% include '_login_widget.html' %}
|
|
@@ -120,6 +117,36 @@
|
|
|
120
117
|
</div>
|
|
121
118
|
</section>
|
|
122
119
|
|
|
120
|
+
<!-- 3.5 Sobre el Autor -->
|
|
121
|
+
<section class="author-section py-5">
|
|
122
|
+
<div class="container">
|
|
123
|
+
<div class="row align-items-center g-4">
|
|
124
|
+
<div class="col-md-1 d-none d-md-block text-center">
|
|
125
|
+
<!-- Opcional: avatar (si no tienes imagen, deja el ícono) -->
|
|
126
|
+
<div class="author-avatar mx-auto">
|
|
127
|
+
<i class="bi bi-person-circle"></i>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="col-md-11">
|
|
131
|
+
<div class="author-card p-4">
|
|
132
|
+
<div class="d-flex flex-wrap align-items-center">
|
|
133
|
+
<h5 class="mb-0 me-3" style="color: var(--brand-primary-color);">Sobre el autor</h5>
|
|
134
|
+
<a href="https://www.linkedin.com/in/fernandolibedinsky" target="_blank" rel="noopener" class="author-linkedin ms-auto">
|
|
135
|
+
<i class="bi bi-linkedin me-1"></i> LinkedIn
|
|
136
|
+
</a>
|
|
137
|
+
</div>
|
|
138
|
+
<p class="author-bio mt-2 mb-0">
|
|
139
|
+
Soy <strong>Fernando Libedinsky</strong>, ingeniero de software y creador de <strong>IAToolkit</strong>.
|
|
140
|
+
<br>Tras una extensa trayectoria en el desarrollo de tecnología, sigo movido por la misma curiosidad que me llevó a programar por primera vez: aprender, explorar y construir cosas nuevas.
|
|
141
|
+
<br>IAToolkit es la continuación de ese impulso, una plataforma creada para conectar rapidamente una empresas con la IA.
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
|
|
123
150
|
<!-- 4. Footer (sin cambios) -->
|
|
124
151
|
<footer class="landing-footer">
|
|
125
152
|
<div class="container">
|