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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from injector import inject
|
|
7
|
+
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
|
+
from iatoolkit.repositories.models import Company, Tool
|
|
9
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
|
+
from iatoolkit.services.sql_service import SqlService
|
|
11
|
+
from iatoolkit.services.excel_service import ExcelService
|
|
12
|
+
from iatoolkit.services.mail_service import MailService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_SYSTEM_TOOLS = [
|
|
16
|
+
{
|
|
17
|
+
"function_name": "iat_generate_excel",
|
|
18
|
+
"description": "Generador de Excel."
|
|
19
|
+
"Genera un archivo Excel (.xlsx) a partir de una lista de diccionarios. "
|
|
20
|
+
"Cada diccionario representa una fila del archivo. "
|
|
21
|
+
"el archivo se guarda en directorio de descargas."
|
|
22
|
+
"retorna diccionario con filename, attachment_token (para enviar archivo por mail)"
|
|
23
|
+
"content_type y download_link",
|
|
24
|
+
"parameters": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"filename": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Nombre del archivo de salida (ejemplo: 'reporte.xlsx')",
|
|
30
|
+
"pattern": "^.+\\.xlsx?$"
|
|
31
|
+
},
|
|
32
|
+
"sheet_name": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Nombre de la hoja dentro del Excel",
|
|
35
|
+
"minLength": 1
|
|
36
|
+
},
|
|
37
|
+
"data": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"description": "Lista de diccionarios. Cada diccionario representa una fila.",
|
|
40
|
+
"minItems": 1,
|
|
41
|
+
"items": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {},
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"anyOf": [
|
|
46
|
+
{"type": "string"},
|
|
47
|
+
{"type": "number"},
|
|
48
|
+
{"type": "boolean"},
|
|
49
|
+
{"type": "null"},
|
|
50
|
+
{
|
|
51
|
+
"type": "string",
|
|
52
|
+
"format": "date"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": ["filename", "sheet_name", "data"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
'function_name': "iat_send_email",
|
|
64
|
+
'description': "iatoolkit mail system. "
|
|
65
|
+
"envia mails cuando un usuario lo solicita.",
|
|
66
|
+
'parameters': {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"recipient": {"type": "string", "description": "email del destinatario"},
|
|
70
|
+
"subject": {"type": "string", "description": "asunto del email"},
|
|
71
|
+
"body": {"type": "string", "description": "HTML del email"},
|
|
72
|
+
"attachments": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"description": "Lista de archivos adjuntos codificados en base64",
|
|
75
|
+
"items": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"filename": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Nombre del archivo con su extensión (ej. informe.pdf)"
|
|
81
|
+
},
|
|
82
|
+
"content": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Contenido del archivo en b64."
|
|
85
|
+
},
|
|
86
|
+
"attachment_token": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "token para descargar el archivo."
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"required": ["filename", "content", "attachment_token"],
|
|
92
|
+
"additionalProperties": False
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": ["recipient", "subject", "body", "attachments"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"function_name": "iat_sql_query",
|
|
101
|
+
"description": "Servicio SQL de IAToolkit: debes utilizar este servicio para todas las consultas a base de datos.",
|
|
102
|
+
"parameters": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"database": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "nombre de la base de datos a consultar: `database_name`"
|
|
108
|
+
},
|
|
109
|
+
"query": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "string con la consulta en sql"
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
"required": ["database", "query"]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ToolService:
|
|
121
|
+
@inject
|
|
122
|
+
def __init__(self,
|
|
123
|
+
llm_query_repo: LLMQueryRepo,
|
|
124
|
+
sql_service: SqlService,
|
|
125
|
+
excel_service: ExcelService,
|
|
126
|
+
mail_service: MailService):
|
|
127
|
+
self.llm_query_repo = llm_query_repo
|
|
128
|
+
self.sql_service = sql_service
|
|
129
|
+
self.excel_service = excel_service
|
|
130
|
+
self.mail_service = mail_service
|
|
131
|
+
|
|
132
|
+
# execution mapper for system tools
|
|
133
|
+
self.system_handlers = {
|
|
134
|
+
"iat_generate_excel": self.excel_service.excel_generator,
|
|
135
|
+
"iat_send_email": self.mail_service.send_mail,
|
|
136
|
+
"iat_sql_query": self.sql_service.exec_sql
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def register_system_tools(self):
|
|
140
|
+
"""Creates or updates system functions in the database."""
|
|
141
|
+
try:
|
|
142
|
+
# delete all system tools
|
|
143
|
+
self.llm_query_repo.delete_system_tools()
|
|
144
|
+
|
|
145
|
+
# create new system tools
|
|
146
|
+
for function in _SYSTEM_TOOLS:
|
|
147
|
+
new_tool = Tool(
|
|
148
|
+
company_id=None,
|
|
149
|
+
system_function=True,
|
|
150
|
+
name=function['function_name'],
|
|
151
|
+
description=function['description'],
|
|
152
|
+
parameters=function['parameters']
|
|
153
|
+
)
|
|
154
|
+
self.llm_query_repo.create_or_update_tool(new_tool)
|
|
155
|
+
|
|
156
|
+
self.llm_query_repo.commit()
|
|
157
|
+
except Exception as e:
|
|
158
|
+
self.llm_query_repo.rollback()
|
|
159
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
160
|
+
|
|
161
|
+
def sync_company_tools(self, company_instance, tools_config: list):
|
|
162
|
+
"""
|
|
163
|
+
Synchronizes tools from YAML config to Database (Create/Update/Delete strategy).
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
# 1. Get existing tools map for later cleanup
|
|
167
|
+
existing_tools = {
|
|
168
|
+
f.name: f for f in self.llm_query_repo.get_company_tools(company_instance.company)
|
|
169
|
+
}
|
|
170
|
+
defined_tool_names = set()
|
|
171
|
+
|
|
172
|
+
# 2. Sync (Create or Update) from Config
|
|
173
|
+
for tool_data in tools_config:
|
|
174
|
+
name = tool_data['function_name']
|
|
175
|
+
defined_tool_names.add(name)
|
|
176
|
+
|
|
177
|
+
# Construct the tool object with current config values
|
|
178
|
+
# We create a new transient object and let the repo merge it
|
|
179
|
+
tool_obj = Tool(
|
|
180
|
+
company_id=company_instance.company.id,
|
|
181
|
+
name=name,
|
|
182
|
+
description=tool_data['description'],
|
|
183
|
+
parameters=tool_data['params'],
|
|
184
|
+
system_function=False
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Always call create_or_update. The repo handles checking for existence by name.
|
|
188
|
+
self.llm_query_repo.create_or_update_tool(tool_obj)
|
|
189
|
+
|
|
190
|
+
# 3. Cleanup: Delete tools present in DB but not in Config
|
|
191
|
+
for name, tool in existing_tools.items():
|
|
192
|
+
# Ensure we don't delete system functions or active tools accidentally if logic changes,
|
|
193
|
+
# though get_company_tools filters by company_id so system functions shouldn't be here usually.
|
|
194
|
+
if not tool.system_function and (name not in defined_tool_names):
|
|
195
|
+
self.llm_query_repo.delete_tool(tool)
|
|
196
|
+
|
|
197
|
+
self.llm_query_repo.commit()
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
self.llm_query_repo.rollback()
|
|
201
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_tools_for_llm(self, company: Company) -> list[dict]:
|
|
205
|
+
"""
|
|
206
|
+
Returns the list of tools (System + Company) formatted for the LLM (OpenAI Schema).
|
|
207
|
+
"""
|
|
208
|
+
tools = []
|
|
209
|
+
# Obtiene tanto las de la empresa como las del sistema (la query del repo debería soportar esto con OR)
|
|
210
|
+
functions = self.llm_query_repo.get_company_tools(company)
|
|
211
|
+
|
|
212
|
+
for function in functions:
|
|
213
|
+
# Clonamos para no modificar el objeto de la sesión SQLAlchemy
|
|
214
|
+
params = function.parameters.copy() if function.parameters else {}
|
|
215
|
+
params["additionalProperties"] = False
|
|
216
|
+
|
|
217
|
+
ai_tool = {
|
|
218
|
+
"type": "function",
|
|
219
|
+
"name": function.name,
|
|
220
|
+
"description": function.description,
|
|
221
|
+
"parameters": params,
|
|
222
|
+
"strict": True
|
|
223
|
+
}
|
|
224
|
+
tools.append(ai_tool)
|
|
225
|
+
return tools
|
|
226
|
+
|
|
227
|
+
def get_system_handler(self, function_name: str):
|
|
228
|
+
return self.system_handlers.get(function_name)
|
|
229
|
+
|
|
230
|
+
def is_system_tool(self, function_name: str) -> bool:
|
|
231
|
+
return function_name in self.system_handlers
|
|
@@ -8,7 +8,7 @@ from injector import inject
|
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
9
|
from iatoolkit.services.i18n_service import I18nService
|
|
10
10
|
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
11
|
-
from iatoolkit.
|
|
11
|
+
from iatoolkit.services.mail_service import MailService
|
|
12
12
|
import logging
|
|
13
13
|
|
|
14
14
|
|
|
@@ -18,11 +18,11 @@ class UserFeedbackService:
|
|
|
18
18
|
profile_repo: ProfileRepo,
|
|
19
19
|
i18n_service: I18nService,
|
|
20
20
|
google_chat_app: GoogleChatApp,
|
|
21
|
-
|
|
21
|
+
mail_service: MailService):
|
|
22
22
|
self.profile_repo = profile_repo
|
|
23
23
|
self.i18n_service = i18n_service
|
|
24
24
|
self.google_chat_app = google_chat_app
|
|
25
|
-
self.
|
|
25
|
+
self.mail_service = mail_service
|
|
26
26
|
|
|
27
27
|
def _send_google_chat_notification(self, space_name: str, message_text: str):
|
|
28
28
|
"""Envía una notificación de feedback a un espacio de Google Chat."""
|
|
@@ -38,13 +38,21 @@ class UserFeedbackService:
|
|
|
38
38
|
except Exception as e:
|
|
39
39
|
logging.exception(f"error sending notification to Google Chat: {e}")
|
|
40
40
|
|
|
41
|
-
def _send_email_notification(self,
|
|
41
|
+
def _send_email_notification(self,
|
|
42
|
+
company_short_name: str,
|
|
43
|
+
destination_email: str,
|
|
44
|
+
company_name: str,
|
|
45
|
+
message_text: str):
|
|
42
46
|
"""Envía una notificación de feedback por correo electrónico."""
|
|
43
47
|
try:
|
|
44
48
|
subject = f"Nuevo Feedback de {company_name}"
|
|
45
49
|
# Convertir el texto plano a un HTML simple para mantener los saltos de línea
|
|
46
50
|
html_body = message_text.replace('\n', '<br>')
|
|
47
|
-
self.
|
|
51
|
+
self.mail_service.send_mail(
|
|
52
|
+
company_short_name=company_short_name,
|
|
53
|
+
to=destination_email,
|
|
54
|
+
subject=subject,
|
|
55
|
+
body=html_body)
|
|
48
56
|
except Exception as e:
|
|
49
57
|
logging.exception(f"error sending email de feedback: {e}")
|
|
50
58
|
|
|
@@ -65,7 +73,11 @@ class UserFeedbackService:
|
|
|
65
73
|
if channel == 'google_chat':
|
|
66
74
|
self._send_google_chat_notification(space_name=destination, message_text=message_text)
|
|
67
75
|
elif channel == 'email':
|
|
68
|
-
self._send_email_notification(
|
|
76
|
+
self._send_email_notification(
|
|
77
|
+
company_short_name=company.short_name,
|
|
78
|
+
destination_email=destination,
|
|
79
|
+
company_name=company.short_name,
|
|
80
|
+
message_text=message_text)
|
|
69
81
|
else:
|
|
70
82
|
logging.warning(f"unknown feedback channel: '{channel}' for company {company.short_name}.")
|
|
71
83
|
|
|
@@ -49,6 +49,24 @@ class UserSessionContextService:
|
|
|
49
49
|
if session_key:
|
|
50
50
|
RedisSessionManager.hset(session_key, 'last_response_id', response_id)
|
|
51
51
|
|
|
52
|
+
def get_initial_response_id(self, company_short_name: str, user_identifier: str) -> Optional[str]:
|
|
53
|
+
"""
|
|
54
|
+
Obtiene el ID de respuesta inicial desde la sesión del usuario.
|
|
55
|
+
Este ID corresponde al estado del LLM justo después de haber configurado el contexto.
|
|
56
|
+
"""
|
|
57
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
58
|
+
if not session_key:
|
|
59
|
+
return None
|
|
60
|
+
return RedisSessionManager.hget(session_key, 'initial_response_id')
|
|
61
|
+
|
|
62
|
+
def save_initial_response_id(self, company_short_name: str, user_identifier: str, response_id: str):
|
|
63
|
+
"""
|
|
64
|
+
Guarda el ID de respuesta inicial en la sesión del usuario.
|
|
65
|
+
"""
|
|
66
|
+
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
67
|
+
if session_key:
|
|
68
|
+
RedisSessionManager.hset(session_key, 'initial_response_id', response_id)
|
|
69
|
+
|
|
52
70
|
def save_context_history(self, company_short_name: str, user_identifier: str, context_history: List[Dict]):
|
|
53
71
|
session_key = self._get_session_key(company_short_name, user_identifier)
|
|
54
72
|
if session_key:
|
|
Binary file
|
|
Binary file
|
|
@@ -53,7 +53,7 @@ $('#feedbackModal').on('hidden.bs.modal', function () {
|
|
|
53
53
|
$('.star').removeClass('active');
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Tool for the star rating system
|
|
57
57
|
window.gfg = function (rating) {
|
|
58
58
|
$('.star').removeClass('active');
|
|
59
59
|
$('.star').each(function (index) {
|
|
@@ -59,7 +59,7 @@ $(document).ready(function () {
|
|
|
59
59
|
cat.questions.forEach(q => contentHtml += `<li>${q}</li>`);
|
|
60
60
|
contentHtml += `</ul>`;
|
|
61
61
|
});
|
|
62
|
-
accordionHtml += createAccordionItem('examples', '
|
|
62
|
+
accordionHtml += createAccordionItem('examples', 'Sample questions', contentHtml, true);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (data.data_sources) {
|
|
@@ -68,7 +68,7 @@ $(document).ready(function () {
|
|
|
68
68
|
contentHtml += `<dt>${p.source}</dt><dd>${p.description}</dd>`;
|
|
69
69
|
});
|
|
70
70
|
contentHtml += `</dl>`;
|
|
71
|
-
accordionHtml += createAccordionItem('sources', '
|
|
71
|
+
accordionHtml += createAccordionItem('sources', 'Data available', contentHtml );
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (data.best_practices) {
|
|
@@ -81,7 +81,7 @@ $(document).ready(function () {
|
|
|
81
81
|
contentHtml += `</dd>`;
|
|
82
82
|
});
|
|
83
83
|
contentHtml += `</dl>`;
|
|
84
|
-
accordionHtml += createAccordionItem('practices', '
|
|
84
|
+
accordionHtml += createAccordionItem('practices', 'Best practices', contentHtml);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if (data.capabilities) {
|
|
@@ -89,7 +89,7 @@ $(document).ready(function () {
|
|
|
89
89
|
contentHtml += `<div class="col-md-6"><h6 class="fw-bold">Puede hacer:</h6><ul>${data.capabilities.can_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
|
|
90
90
|
contentHtml += `<div class="col-md-6"><h6 class="fw-bold">No puede hacer:</h6><ul>${data.capabilities.cannot_do.map(item => `<li>${item}</li>`).join('')}</ul></div>`;
|
|
91
91
|
contentHtml += `</div>`;
|
|
92
|
-
accordionHtml += createAccordionItem('capabilities', '
|
|
92
|
+
accordionHtml += createAccordionItem('capabilities', 'Capabilities and limits', contentHtml);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
container.html(accordionHtml);
|
iatoolkit/static/js/chat_main.js
CHANGED
|
@@ -120,7 +120,6 @@ const handleChatMessage = async function () {
|
|
|
120
120
|
}
|
|
121
121
|
} catch (error) {
|
|
122
122
|
if (error.name === 'AbortError') {
|
|
123
|
-
console.log('Petición abortada por el usuario.');
|
|
124
123
|
|
|
125
124
|
// Usando jQuery estándar para construir el elemento ---
|
|
126
125
|
const icon = $('<i>').addClass('bi bi-stop-circle me-2'); // Icono sin "fill" para un look más ligero
|
|
@@ -230,22 +229,35 @@ const callToolkit = async function(apiPath, data, method, timeoutMs = 500000) {
|
|
|
230
229
|
}
|
|
231
230
|
const response = await fetch(url, fetchOptions);
|
|
232
231
|
clearTimeout(timeoutId);
|
|
232
|
+
|
|
233
|
+
// answer is NOT OK (status != 200)
|
|
233
234
|
if (!response.ok) {
|
|
234
235
|
try {
|
|
235
236
|
// Intentamos leer el error como JSON, que es el formato esperado de nuestra API.
|
|
236
237
|
const errorData = await response.json();
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
|
|
239
|
+
// if it's a iatoolkit error (409 o 400 with a message), shot it on the chat
|
|
240
|
+
if (errorData && (errorData.error_message || errorData.error)) {
|
|
241
|
+
const errorMessage = errorData.error_message || errorData.error || t_js('unknown_server_error');
|
|
242
|
+
const errorIcon = '<i class="bi bi-exclamation-triangle"></i>';
|
|
243
|
+
const endpointError = $('<div>').addClass('error-section').html(errorIcon + `<p>${errorMessage}</p>`);
|
|
244
|
+
displayBotMessage(endpointError);
|
|
245
|
+
} else {
|
|
246
|
+
// if there is not message, we show a generic error message
|
|
247
|
+
throw new Error(`Server error: ${response.status}`);
|
|
248
|
+
}
|
|
241
249
|
} catch (e) {
|
|
242
250
|
// Si response.json() falla, es porque el cuerpo no era JSON (ej. un 502 con HTML).
|
|
243
251
|
// Mostramos un error genérico y más claro para el usuario.
|
|
244
252
|
const errorMessage = `Error de comunicación con el servidor (${response.status}). Por favor, intente de nuevo más tarde.`;
|
|
245
253
|
toastr.error(errorMessage);
|
|
246
254
|
}
|
|
255
|
+
|
|
256
|
+
// stop the flow on the calling function
|
|
247
257
|
return null;
|
|
248
258
|
}
|
|
259
|
+
|
|
260
|
+
// if the answer is OK
|
|
249
261
|
return await response.json();
|
|
250
262
|
} catch (error) {
|
|
251
263
|
clearTimeout(timeoutId);
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
if (elTitle) elTitle.textContent = c.title || '';
|
|
46
46
|
if (elText) elText.innerHTML = c.text || '';
|
|
47
47
|
if (elExample && c.example) {
|
|
48
|
-
elExample.innerHTML = ('
|
|
48
|
+
elExample.innerHTML = (t_js('example') + ': ' + c.example) || '';
|
|
49
49
|
}
|
|
50
50
|
else
|
|
51
51
|
elExample.innerHTML = '';
|
|
@@ -172,7 +172,7 @@ li {
|
|
|
172
172
|
max-width: 75%;
|
|
173
173
|
min-width: 250px;
|
|
174
174
|
|
|
175
|
-
color: var(--brand-danger-text, #
|
|
175
|
+
color: var(--brand-danger-text, #000000); /* Color de texto de error de la marca */
|
|
176
176
|
background-color: var(--brand-danger-bg, #f8d7da); /* Fondo de error de la marca */
|
|
177
177
|
border: 1px solid var(--brand-danger-border, #f5c2c7); /* Borde de error de la marca */
|
|
178
178
|
font-style: italic;
|
|
@@ -104,4 +104,32 @@
|
|
|
104
104
|
margin-bottom: 1.5rem; /* Espacio consistente debajo del título */
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* Links de idioma sobre fondo azul */
|
|
108
|
+
.language-link {
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
color: #ffffff; /* texto blanco */
|
|
111
|
+
padding: 1px 4px;
|
|
112
|
+
font-size: 0.85rem;
|
|
113
|
+
font-weight: 500;
|
|
114
|
+
border-radius: 3px;
|
|
115
|
+
transition: background-color 0.2s ease;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Separador | puede heredar color blanco */
|
|
119
|
+
.language-switcher .mx-1 {
|
|
120
|
+
color: #ffffff;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Hover: blanco translúcido encima del azul */
|
|
124
|
+
.language-link:hover {
|
|
125
|
+
background-color: rgba(255, 255, 255, 0.25);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Idioma activo: un poco más marcado */
|
|
129
|
+
.language-link.active {
|
|
130
|
+
background-color: rgba(255, 255, 255, 0.35);
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
color: #ffffff;
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
|