iatoolkit 0.4.2__py3-none-any.whl → 0.66.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.
- iatoolkit/__init__.py +13 -35
- iatoolkit/base_company.py +74 -8
- iatoolkit/cli_commands.py +15 -23
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +46 -0
- iatoolkit/common/routes.py +141 -0
- iatoolkit/common/session_manager.py +24 -0
- iatoolkit/common/util.py +348 -0
- iatoolkit/company_registry.py +7 -8
- iatoolkit/iatoolkit.py +169 -96
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/call_service.py +140 -0
- iatoolkit/infra/connectors/__init__.py +5 -0
- iatoolkit/infra/connectors/file_connector.py +17 -0
- iatoolkit/infra/connectors/file_connector_factory.py +57 -0
- iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
- iatoolkit/infra/connectors/google_drive_connector.py +68 -0
- iatoolkit/infra/connectors/local_file_connector.py +46 -0
- iatoolkit/infra/connectors/s3_connector.py +33 -0
- iatoolkit/infra/gemini_adapter.py +356 -0
- iatoolkit/infra/google_chat_app.py +57 -0
- iatoolkit/infra/llm_client.py +429 -0
- iatoolkit/infra/llm_proxy.py +139 -0
- iatoolkit/infra/llm_response.py +40 -0
- iatoolkit/infra/mail_app.py +145 -0
- iatoolkit/infra/openai_adapter.py +90 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +144 -0
- iatoolkit/locales/es.yaml +140 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +110 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/llm_query_repo.py +91 -0
- iatoolkit/repositories/models.py +336 -0
- iatoolkit/repositories/profile_repo.py +123 -0
- iatoolkit/repositories/tasks_repo.py +52 -0
- iatoolkit/repositories/vs_repo.py +139 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +6 -6
- iatoolkit/services/branding_service.py +149 -0
- {services → iatoolkit/services}/dispatcher_service.py +39 -99
- {services → iatoolkit/services}/document_service.py +5 -5
- {services → iatoolkit/services}/excel_service.py +27 -21
- {services → iatoolkit/services}/file_processor_service.py +5 -5
- iatoolkit/services/help_content_service.py +30 -0
- {services → iatoolkit/services}/history_service.py +8 -16
- iatoolkit/services/i18n_service.py +104 -0
- {services → iatoolkit/services}/jwt_service.py +18 -27
- iatoolkit/services/language_service.py +77 -0
- {services → iatoolkit/services}/load_documents_service.py +19 -14
- {services → iatoolkit/services}/mail_service.py +5 -5
- iatoolkit/services/onboarding_service.py +43 -0
- {services → iatoolkit/services}/profile_service.py +155 -89
- {services → iatoolkit/services}/prompt_manager_service.py +26 -11
- {services → iatoolkit/services}/query_service.py +142 -104
- {services → iatoolkit/services}/search_service.py +21 -5
- {services → iatoolkit/services}/sql_service.py +24 -6
- {services → iatoolkit/services}/tasks_service.py +10 -10
- iatoolkit/services/user_feedback_service.py +103 -0
- iatoolkit/services/user_session_context_service.py +143 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_filepond.js +85 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +112 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +364 -0
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +592 -0
- iatoolkit/static/styles/chat_modal.css +169 -0
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/llm_output.css +115 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +5 -15
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/about.html +13 -0
- iatoolkit/templates/base.html +65 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +287 -0
- iatoolkit/templates/chat_modals.html +181 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +50 -0
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +104 -0
- iatoolkit/templates/signup.html +76 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +92 -0
- iatoolkit/views/change_password_view.py +117 -0
- iatoolkit/views/external_login_view.py +73 -0
- iatoolkit/views/file_store_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +72 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +61 -0
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +73 -0
- iatoolkit/views/llmquery_api_view.py +57 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +153 -0
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/signup_view.py +94 -0
- iatoolkit/views/tasks_api_view.py +72 -0
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +62 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
- iatoolkit-0.66.2.dist-info/RECORD +119 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.4.2.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import sib_api_v3_sdk
|
|
7
|
+
from sib_api_v3_sdk.rest import ApiException
|
|
8
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
|
+
import os
|
|
10
|
+
import base64
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
MAX_ATTACH_BYTES = int(os.getenv("BREVO_MAX_ATTACH_BYTES", str(5 * 1024 * 1024))) # 5MB seguro
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MailApp:
|
|
17
|
+
def __init__(self,):
|
|
18
|
+
self.configuration = sib_api_v3_sdk.Configuration()
|
|
19
|
+
self.configuration.api_key['api-key'] = os.getenv('BREVO_API_KEY')
|
|
20
|
+
self.mail_api = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(self.configuration))
|
|
21
|
+
self.sender = {"email": "ia@iatoolkit.com", "name": "IA Toolkit"}
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def _strip_data_url_prefix(b64: str) -> str:
|
|
25
|
+
if not isinstance(b64, str):
|
|
26
|
+
return b64
|
|
27
|
+
i = b64.find("base64,")
|
|
28
|
+
return b64[i + 7:] if i != -1 else b64
|
|
29
|
+
|
|
30
|
+
def _normalize_attachments(self, attachments: list[dict] | None):
|
|
31
|
+
if not attachments:
|
|
32
|
+
return None
|
|
33
|
+
sdk_attachments = []
|
|
34
|
+
for idx, a in enumerate(attachments, start=1):
|
|
35
|
+
# 1) claves obligatorias
|
|
36
|
+
if "filename" not in a:
|
|
37
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
38
|
+
f"Adjunto #{idx} inválido: falta 'filename'")
|
|
39
|
+
if "content" not in a:
|
|
40
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
41
|
+
f"Adjunto '{a.get('filename', '(sin nombre)')}' inválido: falta 'content'")
|
|
42
|
+
|
|
43
|
+
name = a["filename"]
|
|
44
|
+
content_b64 = a["content"]
|
|
45
|
+
|
|
46
|
+
# 2) quitar prefijo data URL si vino así
|
|
47
|
+
content_b64 = self._strip_data_url_prefix(content_b64)
|
|
48
|
+
|
|
49
|
+
# 3) validar base64 (y que no esté vacío)
|
|
50
|
+
try:
|
|
51
|
+
raw = base64.b64decode(content_b64, validate=True)
|
|
52
|
+
except Exception:
|
|
53
|
+
logging.error("Adjunto '%s' con base64 inválido (primeros 16 chars: %r)",
|
|
54
|
+
name, str(content_b64)[:16])
|
|
55
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
56
|
+
f"Adjunto '{name}' trae base64 inválido")
|
|
57
|
+
|
|
58
|
+
if not raw:
|
|
59
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
60
|
+
f"Adjunto '{name}' está vacío")
|
|
61
|
+
|
|
62
|
+
# 4) volver a base64 limpio (sin prefijos, sin espacios)
|
|
63
|
+
clean_b64 = base64.b64encode(raw).decode("utf-8")
|
|
64
|
+
|
|
65
|
+
# 5) construir objeto del SDK
|
|
66
|
+
sdk_attachments.append(
|
|
67
|
+
sib_api_v3_sdk.SendSmtpEmailAttachment(
|
|
68
|
+
name=name,
|
|
69
|
+
content=clean_b64
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return sdk_attachments
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def send_email(self,
|
|
76
|
+
to: str,
|
|
77
|
+
subject: str,
|
|
78
|
+
body: str,
|
|
79
|
+
sender: dict = None,
|
|
80
|
+
attachments: list[dict] = None):
|
|
81
|
+
if not sender:
|
|
82
|
+
sender = self.sender
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
sdk_attachments = self._normalize_attachments(attachments)
|
|
86
|
+
email = sib_api_v3_sdk.SendSmtpEmail(
|
|
87
|
+
to=[{"email": to}],
|
|
88
|
+
sender=sender,
|
|
89
|
+
subject=subject,
|
|
90
|
+
html_content=body,
|
|
91
|
+
attachment=sdk_attachments
|
|
92
|
+
)
|
|
93
|
+
api_response = self.mail_api.send_transac_email(email)
|
|
94
|
+
|
|
95
|
+
# Validación de respuesta
|
|
96
|
+
message_id = getattr(api_response, "message_id", None) or getattr(api_response, "messageId", None)
|
|
97
|
+
message_ids = getattr(api_response, "message_ids", None) or getattr(api_response, "messageIds", None)
|
|
98
|
+
if not ((isinstance(message_id, str) and message_id.strip()) or
|
|
99
|
+
(isinstance(message_ids, (list, tuple)) and len(message_ids) > 0)):
|
|
100
|
+
logging.error("MAIL ERROR: Respuesta sin message_id(s): %r", api_response)
|
|
101
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
102
|
+
"Brevo no retornó message_id; el envío podría haber fallado.")
|
|
103
|
+
|
|
104
|
+
return api_response
|
|
105
|
+
|
|
106
|
+
except ApiException as e:
|
|
107
|
+
logging.exception("MAIL ERROR (ApiException): status=%s reason=%s body=%s",
|
|
108
|
+
getattr(e, "status", None), getattr(e, "reason", None), getattr(e, "body", None))
|
|
109
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
110
|
+
f"Error Brevo (status={getattr(e, 'status', 'N/A')}): {getattr(e, 'reason', str(e))}") from e
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logging.exception("MAIL ERROR: %s", str(e))
|
|
113
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
114
|
+
f"No se pudo enviar correo: {str(e)}") from e
|
|
115
|
+
''''
|
|
116
|
+
def send_template_email(self,
|
|
117
|
+
subject: str,
|
|
118
|
+
recipients: list,
|
|
119
|
+
template_name: str,
|
|
120
|
+
context: dict,
|
|
121
|
+
sender=None):
|
|
122
|
+
try:
|
|
123
|
+
# Renderiza el template con el contexto proporcionado
|
|
124
|
+
with self.app.app_context():
|
|
125
|
+
html_message = render_template(template_name, **context)
|
|
126
|
+
|
|
127
|
+
# Crea el mensaje
|
|
128
|
+
msg = Message(
|
|
129
|
+
subject=subject,
|
|
130
|
+
recipients=recipients,
|
|
131
|
+
html=html_message,
|
|
132
|
+
sender=sender or self.app.config.get('MAIL_DEFAULT_SENDER')
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Envía el correo
|
|
136
|
+
# self.send_brevo_email(msg)
|
|
137
|
+
pass
|
|
138
|
+
except jinja2.exceptions.TemplateNotFound:
|
|
139
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
140
|
+
f"Error: No se encontró el template '{template_name}'.")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
143
|
+
f'No se pudo enviar correo: {str(e)}') from e
|
|
144
|
+
|
|
145
|
+
'''
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
from iatoolkit.infra.llm_response import LLMResponse, ToolCall, Usage
|
|
9
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenAIAdapter:
|
|
13
|
+
"""Adaptador para la API de OpenAI"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, openai_client):
|
|
16
|
+
self.client = openai_client
|
|
17
|
+
|
|
18
|
+
def create_response(self,
|
|
19
|
+
model: str,
|
|
20
|
+
input: List[Dict],
|
|
21
|
+
previous_response_id: Optional[str] = None,
|
|
22
|
+
context_history: Optional[List[Dict]] = None,
|
|
23
|
+
tools: Optional[List[Dict]] = None,
|
|
24
|
+
text: Optional[Dict] = None,
|
|
25
|
+
reasoning: Optional[Dict] = None,
|
|
26
|
+
tool_choice: str = "auto") -> LLMResponse:
|
|
27
|
+
"""Llamada a la API de OpenAI y mapeo a estructura común"""
|
|
28
|
+
try:
|
|
29
|
+
# Preparar parámetros para OpenAI
|
|
30
|
+
params = {
|
|
31
|
+
'model': model,
|
|
32
|
+
'input': input
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if previous_response_id:
|
|
36
|
+
params['previous_response_id'] = previous_response_id
|
|
37
|
+
if tools:
|
|
38
|
+
params['tools'] = tools
|
|
39
|
+
if text:
|
|
40
|
+
params['text'] = text
|
|
41
|
+
if reasoning:
|
|
42
|
+
params['reasoning'] = reasoning
|
|
43
|
+
if tool_choice != "auto":
|
|
44
|
+
params['tool_choice'] = tool_choice
|
|
45
|
+
|
|
46
|
+
# Llamar a la API de OpenAI
|
|
47
|
+
openai_response = self.client.responses.create(**params)
|
|
48
|
+
|
|
49
|
+
# Mapear la respuesta a estructura común
|
|
50
|
+
return self._map_openai_response(openai_response)
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
error_message = f"Error calling OpenAI API: {str(e)}"
|
|
54
|
+
logging.error(error_message)
|
|
55
|
+
|
|
56
|
+
# En caso de error de contexto
|
|
57
|
+
if "context_length_exceeded" in str(e):
|
|
58
|
+
error_message = 'Tu consulta supera el limite de contexto. Reinicia el contexto con el boton de la barra superior.'
|
|
59
|
+
|
|
60
|
+
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
61
|
+
|
|
62
|
+
def _map_openai_response(self, openai_response) -> LLMResponse:
|
|
63
|
+
"""Mapear respuesta de OpenAI a estructura común"""
|
|
64
|
+
# Mapear tool calls
|
|
65
|
+
tool_calls = []
|
|
66
|
+
if hasattr(openai_response, 'output') and openai_response.output:
|
|
67
|
+
for tool_call in openai_response.output:
|
|
68
|
+
if hasattr(tool_call, 'type') and tool_call.type == "function_call":
|
|
69
|
+
tool_calls.append(ToolCall(
|
|
70
|
+
call_id=getattr(tool_call, 'call_id', ''),
|
|
71
|
+
type=tool_call.type,
|
|
72
|
+
name=getattr(tool_call, 'name', ''),
|
|
73
|
+
arguments=getattr(tool_call, 'arguments', '{}')
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
# Mapear usage
|
|
77
|
+
usage = Usage(
|
|
78
|
+
input_tokens=openai_response.usage.input_tokens if openai_response.usage else 0,
|
|
79
|
+
output_tokens=openai_response.usage.output_tokens if openai_response.usage else 0,
|
|
80
|
+
total_tokens=openai_response.usage.total_tokens if openai_response.usage else 0
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return LLMResponse(
|
|
84
|
+
id=openai_response.id,
|
|
85
|
+
model=openai_response.model,
|
|
86
|
+
status=openai_response.status,
|
|
87
|
+
output_text=getattr(openai_response, 'output_text', ''),
|
|
88
|
+
output=tool_calls,
|
|
89
|
+
usage=usage
|
|
90
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import redis
|
|
9
|
+
import json
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RedisSessionManager:
|
|
14
|
+
"""
|
|
15
|
+
SessionManager que usa Redis directamente para datos persistentes como llm_history.
|
|
16
|
+
Separado de Flask session para tener control total sobre el ciclo de vida de los datos.
|
|
17
|
+
"""
|
|
18
|
+
_client = None
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _get_client(cls):
|
|
22
|
+
if cls._client is None:
|
|
23
|
+
# Usar exactamente los mismos parámetros que Flask-Session
|
|
24
|
+
url = urlparse(os.environ.get("REDIS_URL"))
|
|
25
|
+
cls._client = redis.Redis(
|
|
26
|
+
host=url.hostname,
|
|
27
|
+
port=url.port,
|
|
28
|
+
password=url.password,
|
|
29
|
+
ssl=(url.scheme == "rediss"),
|
|
30
|
+
ssl_cert_reqs=None,
|
|
31
|
+
decode_responses=True # Importante para strings
|
|
32
|
+
)
|
|
33
|
+
# verify connection
|
|
34
|
+
cls._client.ping()
|
|
35
|
+
info = cls._client.info(section="server")
|
|
36
|
+
db = cls._client.connection_pool.connection_kwargs.get('db', 0)
|
|
37
|
+
return cls._client
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def set(cls, key: str, value: str, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Método set flexible que pasa argumentos opcionales (como ex, nx)
|
|
43
|
+
directamente al cliente de redis.
|
|
44
|
+
"""
|
|
45
|
+
client = cls._get_client()
|
|
46
|
+
# Pasa todos los argumentos de palabra clave adicionales al cliente real
|
|
47
|
+
result = client.set(key, value, **kwargs)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def get(cls, key: str, default: str = ""):
|
|
52
|
+
client = cls._get_client()
|
|
53
|
+
value = client.get(key)
|
|
54
|
+
result = value if value is not None else default
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def hset(cls, key: str, field: str, value: str):
|
|
59
|
+
"""
|
|
60
|
+
Establece un campo en un Hash de Redis.
|
|
61
|
+
"""
|
|
62
|
+
client = cls._get_client()
|
|
63
|
+
return client.hset(key, field, value)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def hget(cls, key: str, field: str):
|
|
67
|
+
"""
|
|
68
|
+
Obtiene el valor de un campo de un Hash de Redis.
|
|
69
|
+
Devuelve None si la clave o el campo no existen.
|
|
70
|
+
"""
|
|
71
|
+
client = cls._get_client()
|
|
72
|
+
return client.hget(key, field)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def hdel(cls, key: str, *fields):
|
|
76
|
+
"""
|
|
77
|
+
Elimina uno o más campos de un Hash de Redis.
|
|
78
|
+
"""
|
|
79
|
+
client = cls._get_client()
|
|
80
|
+
return client.hdel(key, *fields)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def pipeline(cls):
|
|
84
|
+
"""
|
|
85
|
+
Inicia una transacción (pipeline) de Redis.
|
|
86
|
+
"""
|
|
87
|
+
client = cls._get_client()
|
|
88
|
+
return client.pipeline()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def remove(cls, key: str):
|
|
93
|
+
client = cls._get_client()
|
|
94
|
+
result = client.delete(key)
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def exists(cls, key: str) -> bool:
|
|
99
|
+
"""Verifica si una clave existe en Redis."""
|
|
100
|
+
client = cls._get_client()
|
|
101
|
+
# El comando EXISTS de Redis devuelve un entero (0 o 1). Lo convertimos a booleano.
|
|
102
|
+
return bool(client.exists(key))
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def set_json(cls, key: str, value: dict, ex: int = None):
|
|
106
|
+
json_str = json.dumps(value)
|
|
107
|
+
return cls.set(key, json_str, ex=ex)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def get_json(cls, key: str, default: dict = None):
|
|
111
|
+
if default is None:
|
|
112
|
+
default = {}
|
|
113
|
+
|
|
114
|
+
json_str = cls.get(key, "")
|
|
115
|
+
if not json_str:
|
|
116
|
+
return default
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
return json.loads(json_str)
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
logging.warning(f"[RedisSessionManager] Invalid JSON in key '{key}': {json_str}")
|
|
122
|
+
return default
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Language: English
|
|
2
|
+
ui:
|
|
3
|
+
login_widget:
|
|
4
|
+
title: "Sign In"
|
|
5
|
+
welcome_message: "Enter your credentials or register to access Sample Company’s AI platform."
|
|
6
|
+
email_placeholder: "Email address"
|
|
7
|
+
password_placeholder: "Password"
|
|
8
|
+
login_button: "Login"
|
|
9
|
+
forgot_password_link: "Forgot your password?"
|
|
10
|
+
no_account_prompt: "Don't have an account?"
|
|
11
|
+
signup_link: "Sign Up"
|
|
12
|
+
|
|
13
|
+
signup:
|
|
14
|
+
title: "Create an Account"
|
|
15
|
+
subtitle: "Start your journey with us today."
|
|
16
|
+
first_name_label: "First Name"
|
|
17
|
+
last_name_label: "Last Name"
|
|
18
|
+
email_label: "Email Address"
|
|
19
|
+
password_label: "Password"
|
|
20
|
+
confirm_password_label: "Confirm Password"
|
|
21
|
+
signup_button: "Create Account"
|
|
22
|
+
already_have_account: "Already have an account?"
|
|
23
|
+
login_link: "Log In"
|
|
24
|
+
disclaimer: "🔒We value your privacy. Your data will be used exclusively for the operation of the platform."
|
|
25
|
+
|
|
26
|
+
forgot_password:
|
|
27
|
+
title: "Recover Password"
|
|
28
|
+
subtitle: "Enter your email address and we will send you a link to reset your password."
|
|
29
|
+
submit_button: "Send Recovery Link"
|
|
30
|
+
back_to_login: "Back to Login"
|
|
31
|
+
|
|
32
|
+
change_password:
|
|
33
|
+
title: "Create New Password"
|
|
34
|
+
subtitle: "You are changing the password for <strong>{email}</strong>."
|
|
35
|
+
temp_code_label: "Temporary Code"
|
|
36
|
+
temp_code_placeholder: "Check your email"
|
|
37
|
+
new_password_label: "New Password"
|
|
38
|
+
password_instructions: "Must contain at least 8 characters, an uppercase letter, a lowercase letter, a number, and a special character."
|
|
39
|
+
confirm_password_label: "Confirm New Password"
|
|
40
|
+
save_button: "Save Password"
|
|
41
|
+
back_to_home: "Back to home"
|
|
42
|
+
|
|
43
|
+
chat:
|
|
44
|
+
welcome_message: "Hello! How can I help you today?"
|
|
45
|
+
input_placeholder: "Type your query here..."
|
|
46
|
+
prompts_available: "Available prompts"
|
|
47
|
+
|
|
48
|
+
tooltips:
|
|
49
|
+
history: "History of my queries"
|
|
50
|
+
reload_context: "Force Context Reload"
|
|
51
|
+
feedback: "Your feedback is very important"
|
|
52
|
+
usage_guide: "Usage Guide"
|
|
53
|
+
onboarding: "How to ask better questions"
|
|
54
|
+
logout: "Log out"
|
|
55
|
+
use_prompt_assistant: "Use Prompt Assistant"
|
|
56
|
+
attach_files: "Attach files"
|
|
57
|
+
view_attached_files: "View attached files"
|
|
58
|
+
send: "Send"
|
|
59
|
+
stop: "Stop"
|
|
60
|
+
|
|
61
|
+
modals:
|
|
62
|
+
files_title: "Uploaded Files"
|
|
63
|
+
feedback_title: "Your Opinion is Important"
|
|
64
|
+
feedback_prompt: "How useful was the assistant's response?"
|
|
65
|
+
feedback_comment_label: "Your feedback helps us improve:"
|
|
66
|
+
feedback_comment_placeholder: "Write your opinion, suggestions, or comments here..."
|
|
67
|
+
history_title: "Query History"
|
|
68
|
+
history_table_date: "Date"
|
|
69
|
+
history_table_query: "Query"
|
|
70
|
+
loading_history: "Loading history..."
|
|
71
|
+
no_history_found: "No query history found."
|
|
72
|
+
help_title: "AI Assistant User Guide"
|
|
73
|
+
|
|
74
|
+
buttons:
|
|
75
|
+
cancel: "Close"
|
|
76
|
+
send: "Send"
|
|
77
|
+
stop: "Stop"
|
|
78
|
+
|
|
79
|
+
errors:
|
|
80
|
+
auth:
|
|
81
|
+
invalid_password: "The provided password is incorrect."
|
|
82
|
+
user_not_found: "A user with that email address was not found."
|
|
83
|
+
invalid_or_expired_token: "Invalid or expired token."
|
|
84
|
+
session_creation_failed: "Could not create user session."
|
|
85
|
+
authentication_required: "Authentication required. No session cookie or API Key provided."
|
|
86
|
+
invalid_api_key: "Invalid or inactive API Key."
|
|
87
|
+
no_user_identifier_api: "No user_identifier provided for API call."
|
|
88
|
+
templates:
|
|
89
|
+
company_not_found: "Company not found."
|
|
90
|
+
home_template_not_found: "The home page template for the company '{company_name}' is not configured."
|
|
91
|
+
template_not_found: "Template not found: '{template_name}'."
|
|
92
|
+
|
|
93
|
+
processing_error: "An error occurred while processing the custom home page template: {error}"
|
|
94
|
+
general:
|
|
95
|
+
unexpected_error: "An unexpected error has occurred. Please contact support."
|
|
96
|
+
unsupported_language: "The selected language is not supported."
|
|
97
|
+
signup:
|
|
98
|
+
company_not_found: "The company {company_name} does not exist."
|
|
99
|
+
incorrect_password_for_existing_user: "The password for the user {email} is incorrect."
|
|
100
|
+
user_already_registered: "The user with email '{email}' already exists in this company."
|
|
101
|
+
password_mismatch: "The passwords do not match. Please try again."
|
|
102
|
+
change_password:
|
|
103
|
+
token_expired: "The password change link has expired. Please request a new one."
|
|
104
|
+
password_mismatch: "The passwords do not match. Please try again."
|
|
105
|
+
invalid_temp_code: "The temporary code is not valid. Please check it or request a new one."
|
|
106
|
+
forgot_password:
|
|
107
|
+
user_not_registered: "The user with email {email} is not registered."
|
|
108
|
+
verification:
|
|
109
|
+
token_expired: "The verification link has expired. Please contact support if you need a new one."
|
|
110
|
+
user_not_found: "The user you are trying to verify does not exist."
|
|
111
|
+
validation:
|
|
112
|
+
password_too_short: "Password must be at least 8 characters long."
|
|
113
|
+
password_no_uppercase: "Password must contain at least one uppercase letter."
|
|
114
|
+
password_no_lowercase: "Password must contain at least one lowercase letter."
|
|
115
|
+
password_no_digit: "Password must contain at least one number."
|
|
116
|
+
password_no_special_char: "Password must contain at least one special character."
|
|
117
|
+
|
|
118
|
+
api_responses:
|
|
119
|
+
context_reloaded_success: "The context has been successfully reloaded."
|
|
120
|
+
|
|
121
|
+
flash_messages:
|
|
122
|
+
password_changed_success: "Your password has been successfully reset. You can now log in."
|
|
123
|
+
login_required: "Please log in to continue."
|
|
124
|
+
signup_success: "Registration successful. Please check your email to verify your account."
|
|
125
|
+
user_associated_success: "Existing user successfully associated with the new company."
|
|
126
|
+
account_verified_success: "Your account has been successfully verified. Welcome!"
|
|
127
|
+
forgot_password_success: "If your email is registered, you will receive a link to reset your password."
|
|
128
|
+
|
|
129
|
+
# Keys specifically for JavaScript
|
|
130
|
+
js_messages:
|
|
131
|
+
feedback_sent_success_title: "Feedback Sent"
|
|
132
|
+
feedback_sent_success_body: "Thank you for your feedback!"
|
|
133
|
+
feedback_sent_error: "Could not send feedback, please try again."
|
|
134
|
+
feedback_rating_error: "Please rate the assistant using the stars."
|
|
135
|
+
feedback_comment_error: "Please write your comment before sending."
|
|
136
|
+
context_reloaded: "Context has been reloaded."
|
|
137
|
+
error_loading_history: "An error occurred while loading the history."
|
|
138
|
+
request_aborted: "The response generation has been stopped."
|
|
139
|
+
processing_error: "An error occurred while processing the request."
|
|
140
|
+
server_comm_error: "Server communication error ({status}). Please try again later."
|
|
141
|
+
network_error: "A network error occurred. Please try again in a few moments."
|
|
142
|
+
unknown_server_error: "Unknown server error."
|
|
143
|
+
loading: "Loading..."
|
|
144
|
+
reload_init: "init reloading context in background..."
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# locales/es.yaml
|
|
2
|
+
ui:
|
|
3
|
+
login_widget:
|
|
4
|
+
title: "Iniciar Sesión"
|
|
5
|
+
welcome_message: "Ingresa tus credenciales o registrate para acceder a la plataforma IA de Sample Company."
|
|
6
|
+
email_placeholder: "Correo electrónico"
|
|
7
|
+
login_button: "Acceder"
|
|
8
|
+
forgot_password_link: "¿Olvidaste tu contraseña?"
|
|
9
|
+
no_account_prompt: "¿No tienes una cuenta?"
|
|
10
|
+
signup_link: "Regístrate"
|
|
11
|
+
|
|
12
|
+
signup:
|
|
13
|
+
title: "Crear una Cuenta"
|
|
14
|
+
subtitle: "Comienza tu viaje con nosotros hoy."
|
|
15
|
+
first_name_label: "Nombre"
|
|
16
|
+
last_name_label: "Apellido"
|
|
17
|
+
email_label: "Correo Electrónico"
|
|
18
|
+
password_label: "Contraseña"
|
|
19
|
+
confirm_password_label: "Confirmar Contraseña"
|
|
20
|
+
signup_button: "Crear Cuenta"
|
|
21
|
+
already_have_account: "¿Ya tienes una cuenta?"
|
|
22
|
+
login_link: "Inicia Sesión"
|
|
23
|
+
disclaimer: "🔒 Valoramos tu privacidad. Tus datos se usarán exclusivamente para el funcionamiento de la plataforma."
|
|
24
|
+
|
|
25
|
+
forgot_password:
|
|
26
|
+
title: "Recuperar Contraseña"
|
|
27
|
+
subtitle: "Ingresa tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña."
|
|
28
|
+
submit_button: "Enviar Enlace de Recuperación"
|
|
29
|
+
back_to_login: "Volver a Iniciar Sesión"
|
|
30
|
+
|
|
31
|
+
change_password:
|
|
32
|
+
title: "Crear Nueva Contraseña"
|
|
33
|
+
subtitle: "Estás cambiando la contraseña para <strong>{email}</strong>."
|
|
34
|
+
temp_code_label: "Código Temporal"
|
|
35
|
+
temp_code_placeholder: "Revisa tu correo electrónico"
|
|
36
|
+
new_password_label: "Nueva Contraseña"
|
|
37
|
+
password_instructions: "Debe contener al menos 8 caracteres, mayúscula, minúscula, número y un carácter especial."
|
|
38
|
+
confirm_password_label: "Confirmar Nueva Contraseña"
|
|
39
|
+
save_button: "Guardar Contraseña"
|
|
40
|
+
back_to_home: "Volver al inicio"
|
|
41
|
+
|
|
42
|
+
chat:
|
|
43
|
+
welcome_message: "¡Hola! ¿En qué te puedo ayudar hoy?"
|
|
44
|
+
input_placeholder: "Escribe tu consulta aquí..."
|
|
45
|
+
prompts_available: "Prompts disponibles"
|
|
46
|
+
|
|
47
|
+
tooltips:
|
|
48
|
+
history: "Historial con mis consultas"
|
|
49
|
+
reload_context: "Forzar Recarga de Contexto"
|
|
50
|
+
feedback: "Tu feedback es muy importante"
|
|
51
|
+
usage_guide: "Guía de Uso"
|
|
52
|
+
onboarding: "Cómo preguntar mejor"
|
|
53
|
+
logout: "Cerrar sesión"
|
|
54
|
+
use_prompt_assistant: "Usar Asistente de Prompts"
|
|
55
|
+
attach_files: "Adjuntar archivos"
|
|
56
|
+
view_attached_files: "Ver archivos adjuntos"
|
|
57
|
+
modals:
|
|
58
|
+
files_title: "Archivos Cargados"
|
|
59
|
+
feedback_title: "Tu Opinión es Importante"
|
|
60
|
+
feedback_prompt: "¿Qué tan útil fue la respuesta del asistente?"
|
|
61
|
+
feedback_comment_label: "Tu comentario nos ayuda a mejorar:"
|
|
62
|
+
feedback_comment_placeholder: "Escribe aquí tu opinión, sugerencias o comentarios..."
|
|
63
|
+
history_title: "Historial de Consultas"
|
|
64
|
+
history_table_date: "Fecha"
|
|
65
|
+
history_table_query: "Consulta"
|
|
66
|
+
loading_history: "Cargando historial..."
|
|
67
|
+
no_history_found: "No se encontró historial de consultas."
|
|
68
|
+
help_title: "Guía de uso del Asistente IA"
|
|
69
|
+
|
|
70
|
+
buttons:
|
|
71
|
+
cancel: "Cerrar"
|
|
72
|
+
send: "Enviar"
|
|
73
|
+
stop: "Detener"
|
|
74
|
+
|
|
75
|
+
errors:
|
|
76
|
+
auth:
|
|
77
|
+
invalid_password: "La contraseña proporcionada es incorrecta."
|
|
78
|
+
user_not_found: "No se encontró un usuario con ese correo."
|
|
79
|
+
invalid_or_expired_token: "Token inválido o expirado."
|
|
80
|
+
session_creation_failed: "No se pudo crear la sesión del usuario."
|
|
81
|
+
authentication_required: "Autenticación requerida. No se proporcionó cookie de sesión o clave de API."
|
|
82
|
+
invalid_api_key: "Clave de API inválida o inactiva."
|
|
83
|
+
no_user_identifier_api: "No se proporcionó user_identifier para la llamada a la API."
|
|
84
|
+
templates:
|
|
85
|
+
company_not_found: "Empresa no encontrada."
|
|
86
|
+
home_template_not_found: "La plantilla de la página de inicio para la empresa '{company_name}' no está configurada."
|
|
87
|
+
template_not_found: "No se encontro el template: '{template_name}'."
|
|
88
|
+
processing_error: "Ocurrió un error al procesar la plantilla personalizada de la página de inicio: {error}"
|
|
89
|
+
|
|
90
|
+
general:
|
|
91
|
+
unexpected_error: "Ha ocurrido un error inesperado. Por favor, contacta a soporte."
|
|
92
|
+
unsupported_language: "El idioma seleccionado no es válido."
|
|
93
|
+
signup:
|
|
94
|
+
company_not_found: "La empresa {company_name} no existe."
|
|
95
|
+
incorrect_password_for_existing_user: "La contraseña para el usuario {email} es incorrecta."
|
|
96
|
+
user_already_registered: "El usuario con email '{email}' ya existe en esta empresa."
|
|
97
|
+
password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."
|
|
98
|
+
change_password:
|
|
99
|
+
token_expired: "El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo."
|
|
100
|
+
password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo nuevamente."
|
|
101
|
+
invalid_temp_code: "El código temporal no es válido. Por favor, verifica o solicita uno nuevo."
|
|
102
|
+
forgot_password:
|
|
103
|
+
user_not_registered: "El usuario con correo {email} no está registrado."
|
|
104
|
+
verification:
|
|
105
|
+
token_expired: "El enlace de verificación ha expirado. Por favor, contacta a soporte si necesitas uno nuevo."
|
|
106
|
+
user_not_found: "El usuario que intentas verificar no existe."
|
|
107
|
+
validation:
|
|
108
|
+
password_too_short: "La contraseña debe tener al menos 8 caracteres."
|
|
109
|
+
password_no_uppercase: "La contraseña debe tener al menos una letra mayúscula."
|
|
110
|
+
password_no_lowercase: "La contraseña debe tener al menos una letra minúscula."
|
|
111
|
+
password_no_digit: "La contraseña debe tener al menos un número."
|
|
112
|
+
password_no_special_char: "La contraseña debe tener al menos un carácter especial."
|
|
113
|
+
|
|
114
|
+
api_responses:
|
|
115
|
+
context_reloaded_success: "El contexto se ha recargado con éxito."
|
|
116
|
+
|
|
117
|
+
flash_messages:
|
|
118
|
+
password_changed_success: "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
|
|
119
|
+
signup_success: "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."
|
|
120
|
+
user_associated_success: "Usuario existente asociado exitosamente a la nueva empresa."
|
|
121
|
+
account_verified_success: "Tu cuenta ha sido verificada exitosamente. ¡Bienvenido!"
|
|
122
|
+
forgot_password_success: "Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña."
|
|
123
|
+
|
|
124
|
+
# Claves específicas para JavaScript
|
|
125
|
+
js_messages:
|
|
126
|
+
feedback_sent_success_title: "Feedback Enviado"
|
|
127
|
+
feedback_sent_success_body: "¡Gracias por tu comentario!"
|
|
128
|
+
feedback_sent_error: "No se pudo enviar el feedback, por favor intente nuevamente."
|
|
129
|
+
feedback_rating_error: "Por favor, califica al asistente con las estrellas."
|
|
130
|
+
feedback_comment_error: "Por favor, escribe tu comentario antes de enviar."
|
|
131
|
+
context_reloaded: "El contexto ha sido recargado."
|
|
132
|
+
error_loading_history: "Ocurrió un error al cargar el historial."
|
|
133
|
+
request_aborted: "La generación de la respuesta ha sido detenida."
|
|
134
|
+
processing_error: "Ocurrió un error al procesar la solicitud."
|
|
135
|
+
server_comm_error: "Error de comunicación con el servidor ({status}). Por favor, intente de nuevo más tarde."
|
|
136
|
+
network_error: "Ocurrió un error de red. Por favor, inténtalo de nuevo en unos momentos."
|
|
137
|
+
unknown_server_error: "Error desconocido del servidor."
|
|
138
|
+
loading: "Cargando..."
|
|
139
|
+
reload_init: "Iniciando recarga de contexto en segundo plano..."
|
|
140
|
+
|