iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__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/__init__.py +27 -35
- iatoolkit/base_company.py +3 -35
- iatoolkit/cli_commands.py +18 -47
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +48 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +39 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +138 -0
- iatoolkit/common/session_manager.py +26 -0
- iatoolkit/common/util.py +353 -0
- iatoolkit/company_registry.py +66 -29
- iatoolkit/core.py +514 -0
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/brevo_mail_app.py +123 -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/google_chat_app.py +57 -0
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
- iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
- iatoolkit/infra/llm_proxy.py +268 -0
- iatoolkit/infra/llm_response.py +45 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +222 -0
- iatoolkit/locales/es.yaml +225 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +187 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +105 -0
- iatoolkit/repositories/models.py +279 -0
- iatoolkit/repositories/profile_repo.py +171 -0
- iatoolkit/repositories/vs_repo.py +150 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +7 -7
- iatoolkit/services/branding_service.py +153 -0
- iatoolkit/services/company_context_service.py +214 -0
- iatoolkit/services/configuration_service.py +375 -0
- iatoolkit/services/dispatcher_service.py +134 -0
- {services → iatoolkit/services}/document_service.py +20 -8
- iatoolkit/services/embedding_service.py +148 -0
- iatoolkit/services/excel_service.py +156 -0
- {services → iatoolkit/services}/file_processor_service.py +36 -21
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +80 -0
- iatoolkit/services/language_service.py +89 -0
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/services/llm_client_service.py +438 -0
- iatoolkit/services/load_documents_service.py +174 -0
- iatoolkit/services/mail_service.py +213 -0
- {services → iatoolkit/services}/profile_service.py +200 -101
- iatoolkit/services/prompt_service.py +303 -0
- iatoolkit/services/query_service.py +467 -0
- iatoolkit/services/search_service.py +55 -0
- iatoolkit/services/sql_service.py +169 -0
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +117 -0
- iatoolkit/services/user_session_context_service.py +213 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +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 +110 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +401 -0
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +103 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +38 -0
- iatoolkit/static/styles/chat_iatoolkit.css +559 -0
- iatoolkit/static/styles/chat_modal.css +133 -0
- iatoolkit/static/styles/chat_public.css +135 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +398 -0
- iatoolkit/static/styles/llm_output.css +148 -0
- iatoolkit/static/styles/onboarding.css +176 -0
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +30 -23
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +45 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +78 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +337 -0
- iatoolkit/templates/chat_modals.html +185 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +51 -0
- iatoolkit/templates/onboarding_shell.html +106 -0
- iatoolkit/templates/signup.html +79 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +96 -0
- iatoolkit/views/change_password_view.py +116 -0
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +75 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +63 -0
- iatoolkit/views/init_context_api_view.py +74 -0
- iatoolkit/views/llmquery_api_view.py +59 -0
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/load_document_api_view.py +65 -0
- iatoolkit/views/login_view.py +170 -0
- iatoolkit/views/logout_api_view.py +57 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +100 -0
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +60 -0
- iatoolkit-0.107.4.dist-info/METADATA +268 -0
- iatoolkit-0.107.4.dist-info/RECORD +132 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
- iatoolkit/iatoolkit.py +0 -413
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.3.9.dist-info/METADATA +0 -252
- iatoolkit-0.3.9.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/dispatcher_service.py +0 -351
- services/excel_service.py +0 -98
- services/history_service.py +0 -45
- services/jwt_service.py +0 -91
- services/load_documents_service.py +0 -212
- services/mail_service.py +0 -62
- services/prompt_manager_service.py +0 -172
- services/query_service.py +0 -334
- services/search_service.py +0 -32
- services/sql_service.py +0 -42
- services/tasks_service.py +0 -188
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import text
|
|
7
|
+
from injector import inject
|
|
8
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
|
+
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
10
|
+
from iatoolkit.services.embedding_service import EmbeddingService
|
|
11
|
+
from iatoolkit.repositories.models import Document, VSDoc, Company
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VSRepo:
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
db_manager: DatabaseManager,
|
|
19
|
+
embedding_service: EmbeddingService):
|
|
20
|
+
self.session = db_manager.get_session()
|
|
21
|
+
self.embedding_service = embedding_service
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def add_document(self, company_short_name, vs_chunk_list: list[VSDoc]):
|
|
25
|
+
try:
|
|
26
|
+
for doc in vs_chunk_list:
|
|
27
|
+
# calculate the embedding for the text
|
|
28
|
+
doc.embedding = self.embedding_service.embed_text(company_short_name, doc.text)
|
|
29
|
+
self.session.add(doc)
|
|
30
|
+
self.session.commit()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logging.error(f"Error while inserting embedding chunk list: {str(e)}")
|
|
33
|
+
self.session.rollback()
|
|
34
|
+
raise IAToolkitException(IAToolkitException.ErrorType.VECTOR_STORE_ERROR,
|
|
35
|
+
f"Error while inserting embedding chunk list: {str(e)}")
|
|
36
|
+
|
|
37
|
+
def query(self,
|
|
38
|
+
company_short_name: str,
|
|
39
|
+
query_text: str,
|
|
40
|
+
n_results=5,
|
|
41
|
+
metadata_filter=None
|
|
42
|
+
) -> list[Document]:
|
|
43
|
+
"""
|
|
44
|
+
search documents similar to the query for a company
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
company_short_name: The company's unique short name.
|
|
48
|
+
query_text: query text
|
|
49
|
+
n_results: max number of results to return
|
|
50
|
+
metadata_filter: (e.g., {"document_type": "certificate"})
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
list of documents matching the query and filters
|
|
54
|
+
"""
|
|
55
|
+
# Generate the embedding with the query text for the specific company
|
|
56
|
+
try:
|
|
57
|
+
query_embedding = self.embedding_service.embed_text(company_short_name, query_text)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logging.error(f"error while creating text embedding: {str(e)}")
|
|
60
|
+
raise IAToolkitException(IAToolkitException.ErrorType.EMBEDDING_ERROR,
|
|
61
|
+
f"embedding error: {str(e)}")
|
|
62
|
+
|
|
63
|
+
sql_query, params = None, None
|
|
64
|
+
try:
|
|
65
|
+
# Get company ID from its short name for the SQL query
|
|
66
|
+
company = self.session.query(Company).filter(Company.short_name == company_short_name).one_or_none()
|
|
67
|
+
if not company:
|
|
68
|
+
raise IAToolkitException(IAToolkitException.ErrorType.VECTOR_STORE_ERROR,
|
|
69
|
+
f"Company with short name '{company_short_name}' not found.")
|
|
70
|
+
|
|
71
|
+
# build the SQL query
|
|
72
|
+
sql_query_parts = ["""
|
|
73
|
+
SELECT iat_documents.id, \
|
|
74
|
+
iat_documents.filename, \
|
|
75
|
+
iat_documents.content, \
|
|
76
|
+
iat_documents.content_b64, \
|
|
77
|
+
iat_documents.meta
|
|
78
|
+
FROM iat_vsdocs, \
|
|
79
|
+
iat_documents
|
|
80
|
+
WHERE iat_vsdocs.company_id = :company_id
|
|
81
|
+
AND iat_vsdocs.document_id = iat_documents.id \
|
|
82
|
+
"""]
|
|
83
|
+
|
|
84
|
+
# query parameters
|
|
85
|
+
params = {
|
|
86
|
+
"company_id": company.id,
|
|
87
|
+
"query_embedding": query_embedding,
|
|
88
|
+
"n_results": n_results
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# add metadata filter, if exists
|
|
93
|
+
if metadata_filter and isinstance(metadata_filter, dict):
|
|
94
|
+
for key, value in metadata_filter.items():
|
|
95
|
+
# Usar el operador ->> para extraer el valor del JSON como texto.
|
|
96
|
+
# La clave del JSON se interpola directamente.
|
|
97
|
+
# El valor se pasa como parámetro para evitar inyección SQL.
|
|
98
|
+
param_name = f"value_{key}_filter"
|
|
99
|
+
sql_query_parts.append(f" AND documents.meta->>'{key}' = :{param_name}")
|
|
100
|
+
params[param_name] = str(value) # parametros como string
|
|
101
|
+
|
|
102
|
+
# join all the query parts
|
|
103
|
+
sql_query = "".join(sql_query_parts)
|
|
104
|
+
|
|
105
|
+
# add sorting and limit of results
|
|
106
|
+
sql_query += " ORDER BY embedding <-> CAST(:query_embedding AS VECTOR) LIMIT :n_results"
|
|
107
|
+
|
|
108
|
+
logging.debug(f"Executing SQL query: {sql_query}")
|
|
109
|
+
logging.debug(f"With parameters: {params}")
|
|
110
|
+
|
|
111
|
+
# execute the query
|
|
112
|
+
result = self.session.execute(text(sql_query), params)
|
|
113
|
+
|
|
114
|
+
rows = result.fetchall()
|
|
115
|
+
vs_documents = []
|
|
116
|
+
|
|
117
|
+
for row in rows:
|
|
118
|
+
# create the document object with the data
|
|
119
|
+
meta_data = row[4] if len(row) > 4 and row[4] is not None else {}
|
|
120
|
+
doc = Document(
|
|
121
|
+
id=row[0],
|
|
122
|
+
company_id=company.id,
|
|
123
|
+
filename=row[1],
|
|
124
|
+
content=row[2],
|
|
125
|
+
content_b64=row[3],
|
|
126
|
+
meta=meta_data
|
|
127
|
+
)
|
|
128
|
+
vs_documents.append(doc)
|
|
129
|
+
|
|
130
|
+
return self.remove_duplicates_by_id(vs_documents)
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logging.error(f"Error en la consulta de documentos: {str(e)}")
|
|
134
|
+
logging.error(f"Failed SQL: {sql_query}")
|
|
135
|
+
logging.error(f"Failed params: {params}")
|
|
136
|
+
raise IAToolkitException(IAToolkitException.ErrorType.VECTOR_STORE_ERROR,
|
|
137
|
+
f"Error en la consulta: {str(e)}")
|
|
138
|
+
finally:
|
|
139
|
+
self.session.close()
|
|
140
|
+
|
|
141
|
+
def remove_duplicates_by_id(self, objects):
|
|
142
|
+
unique_by_id = {}
|
|
143
|
+
result = []
|
|
144
|
+
|
|
145
|
+
for obj in objects:
|
|
146
|
+
if obj.id not in unique_by_id:
|
|
147
|
+
unique_by_id[obj.id] = True
|
|
148
|
+
result.append(obj)
|
|
149
|
+
|
|
150
|
+
return result
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import request
|
|
7
|
+
from injector import inject
|
|
8
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
9
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
10
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
11
|
+
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
12
|
+
from iatoolkit.repositories.models import AccessLog
|
|
13
|
+
from flask import request
|
|
14
|
+
import logging
|
|
15
|
+
import hashlib
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AuthService:
|
|
19
|
+
"""
|
|
20
|
+
Centralized service for handling authentication for all incoming requests.
|
|
21
|
+
It determines the user's identity based on either a Flask session cookie or an API Key.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@inject
|
|
25
|
+
def __init__(self, profile_service: ProfileService,
|
|
26
|
+
jwt_service: JWTService,
|
|
27
|
+
db_manager: DatabaseManager,
|
|
28
|
+
i18n_service: I18nService
|
|
29
|
+
):
|
|
30
|
+
self.profile_service = profile_service
|
|
31
|
+
self.jwt_service = jwt_service
|
|
32
|
+
self.db_manager = db_manager
|
|
33
|
+
self.i18n_service = i18n_service
|
|
34
|
+
|
|
35
|
+
def login_local_user(self, company_short_name: str, email: str, password: str) -> dict:
|
|
36
|
+
# try to autenticate a local user, register the event and return the result
|
|
37
|
+
auth_response = self.profile_service.login(
|
|
38
|
+
company_short_name=company_short_name,
|
|
39
|
+
email=email,
|
|
40
|
+
password=password,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if not auth_response.get('success'):
|
|
44
|
+
self.log_access(
|
|
45
|
+
company_short_name=company_short_name,
|
|
46
|
+
user_identifier=email,
|
|
47
|
+
auth_type='local',
|
|
48
|
+
outcome='failure',
|
|
49
|
+
reason_code='INVALID_CREDENTIALS',
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
self.log_access(
|
|
53
|
+
company_short_name=company_short_name,
|
|
54
|
+
auth_type='local',
|
|
55
|
+
outcome='success',
|
|
56
|
+
user_identifier=auth_response.get('user_identifier')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return auth_response
|
|
60
|
+
|
|
61
|
+
def redeem_token_for_session(self, company_short_name: str, token: str) -> dict:
|
|
62
|
+
# redeem a token for a session, register the event and return the result
|
|
63
|
+
payload = self.jwt_service.validate_chat_jwt(token)
|
|
64
|
+
|
|
65
|
+
if not payload:
|
|
66
|
+
self.log_access(
|
|
67
|
+
company_short_name=company_short_name,
|
|
68
|
+
auth_type='redeem_token',
|
|
69
|
+
outcome='failure',
|
|
70
|
+
reason_code='JWT_INVALID'
|
|
71
|
+
)
|
|
72
|
+
return {'success': False, 'error': self.i18n_service.t('errors.auth.invalid_or_expired_token')}
|
|
73
|
+
|
|
74
|
+
# 2. if token is valid, extract the user_identifier
|
|
75
|
+
user_identifier = payload.get('user_identifier')
|
|
76
|
+
try:
|
|
77
|
+
# create the Flask session
|
|
78
|
+
self.profile_service.set_session_for_user(company_short_name, user_identifier)
|
|
79
|
+
self.log_access(
|
|
80
|
+
company_short_name=company_short_name,
|
|
81
|
+
auth_type='redeem_token',
|
|
82
|
+
outcome='success',
|
|
83
|
+
user_identifier=user_identifier
|
|
84
|
+
)
|
|
85
|
+
return {'success': True, 'user_identifier': user_identifier}
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logging.error(f"error creeating session for Token of {user_identifier}: {e}")
|
|
88
|
+
self.log_access(
|
|
89
|
+
company_short_name=company_short_name,
|
|
90
|
+
auth_type='redeem_token',
|
|
91
|
+
outcome='failure',
|
|
92
|
+
reason_code='SESSION_CREATION_FAILED',
|
|
93
|
+
user_identifier=user_identifier
|
|
94
|
+
)
|
|
95
|
+
return {'success': False, 'error': self.i18n_service.t('errors.auth.session_creation_failed')}
|
|
96
|
+
|
|
97
|
+
def verify(self, anonymous: bool = False) -> dict:
|
|
98
|
+
"""
|
|
99
|
+
Verifies the current request and identifies the user.
|
|
100
|
+
If anonymous is True the non-presence of use_identifier is ignored
|
|
101
|
+
|
|
102
|
+
Returns a dictionary with:
|
|
103
|
+
- success: bool
|
|
104
|
+
- user_identifier: str (if successful)
|
|
105
|
+
- company_short_name: str (if successful)
|
|
106
|
+
- error_message: str (on failure)
|
|
107
|
+
- status_code: int (on failure)
|
|
108
|
+
"""
|
|
109
|
+
# --- Priority 1: Check for a valid Flask web session ---
|
|
110
|
+
session_info = self.profile_service.get_current_session_info()
|
|
111
|
+
if session_info and session_info.get('user_identifier'):
|
|
112
|
+
# User is authenticated via a web session cookie.
|
|
113
|
+
return {
|
|
114
|
+
"success": True,
|
|
115
|
+
"company_short_name": session_info['company_short_name'],
|
|
116
|
+
"user_identifier": session_info['user_identifier'],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# --- Priority 2: Check for a valid API Key in headers ---
|
|
120
|
+
api_key = None
|
|
121
|
+
auth = request.headers.get('Authorization', '')
|
|
122
|
+
if isinstance(auth, str) and auth.lower().startswith('bearer '):
|
|
123
|
+
api_key = auth.split(' ', 1)[1].strip()
|
|
124
|
+
|
|
125
|
+
if not api_key:
|
|
126
|
+
# --- Failure: No valid credentials found ---
|
|
127
|
+
logging.info(f"Authentication required. No session cookie or API Key provided.")
|
|
128
|
+
return {"success": False,
|
|
129
|
+
"error_message": self.i18n_service.t('errors.auth.authentication_required'),
|
|
130
|
+
"status_code": 401}
|
|
131
|
+
|
|
132
|
+
# check if the api-key is valid and active
|
|
133
|
+
api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
|
|
134
|
+
if not api_key_entry:
|
|
135
|
+
logging.error(f"Invalid or inactive IAToolkit API Key: {api_key}")
|
|
136
|
+
return {"success": False,
|
|
137
|
+
"error_message": self.i18n_service.t('errors.auth.invalid_api_key'),
|
|
138
|
+
"status_code": 402}
|
|
139
|
+
|
|
140
|
+
# get the company from the api_key_entry
|
|
141
|
+
company = api_key_entry.company
|
|
142
|
+
|
|
143
|
+
# For API calls, the external_user_id must be provided in the request.
|
|
144
|
+
data = request.get_json(silent=True) or {}
|
|
145
|
+
user_identifier = data.get('user_identifier', '')
|
|
146
|
+
if not anonymous and not user_identifier:
|
|
147
|
+
logging.info(f"No user_identifier provided for API call.")
|
|
148
|
+
return {"success": False,
|
|
149
|
+
"error_message": self.i18n_service.t('errors.auth.no_user_identifier_api'),
|
|
150
|
+
"status_code": 403}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"success": True,
|
|
154
|
+
"company_short_name": company.short_name,
|
|
155
|
+
"user_identifier": user_identifier
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def log_access(self,
|
|
160
|
+
company_short_name: str,
|
|
161
|
+
auth_type: str,
|
|
162
|
+
outcome: str,
|
|
163
|
+
user_identifier: str = None,
|
|
164
|
+
reason_code: str = None):
|
|
165
|
+
"""
|
|
166
|
+
Registra un intento de acceso en la base de datos.
|
|
167
|
+
Es "best-effort" y no debe interrumpir el flujo de autenticación.
|
|
168
|
+
"""
|
|
169
|
+
session = self.db_manager.scoped_session()
|
|
170
|
+
try:
|
|
171
|
+
# Capturar datos del contexto de la petición de Flask
|
|
172
|
+
source_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
173
|
+
path = request.path
|
|
174
|
+
ua = request.headers.get('User-Agent', '')
|
|
175
|
+
ua_hash = hashlib.sha256(ua.encode()).hexdigest()[:16] if ua else None
|
|
176
|
+
|
|
177
|
+
# Crear la entrada de log
|
|
178
|
+
log_entry = AccessLog(
|
|
179
|
+
company_short_name=company_short_name,
|
|
180
|
+
user_identifier=user_identifier,
|
|
181
|
+
auth_type=auth_type,
|
|
182
|
+
outcome=outcome,
|
|
183
|
+
reason_code=reason_code,
|
|
184
|
+
source_ip=source_ip,
|
|
185
|
+
user_agent_hash=ua_hash,
|
|
186
|
+
request_path=path,
|
|
187
|
+
)
|
|
188
|
+
session.add(log_entry)
|
|
189
|
+
session.commit()
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logging.error(f"error writting to AccessLog: {e}", exc_info=False)
|
|
193
|
+
session.rollback()
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import time
|
|
8
8
|
import logging
|
|
9
9
|
from injector import inject
|
|
10
|
-
from services.query_service import QueryService
|
|
11
|
-
from repositories.profile_repo import ProfileRepo
|
|
12
|
-
from common.exceptions import IAToolkitException
|
|
10
|
+
from iatoolkit.services.query_service import QueryService
|
|
11
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
12
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BenchmarkService:
|
|
@@ -65,7 +65,7 @@ class BenchmarkService:
|
|
|
65
65
|
|
|
66
66
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
67
67
|
if not company:
|
|
68
|
-
raise IAToolkitException(IAToolkitException.ErrorType.CONFIG_ERROR, "Compañía
|
|
68
|
+
raise IAToolkitException(IAToolkitException.ErrorType.CONFIG_ERROR, f"Compañía {company_short_name} no encontrada.")
|
|
69
69
|
|
|
70
70
|
total_rows = len(df)
|
|
71
71
|
logging.info(f"Iniciando benchmark para {total_rows} casos de prueba desde el archivo: {file_path}")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from iatoolkit.repositories.models import Company
|
|
7
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
8
|
+
from injector import inject
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BrandingService:
|
|
12
|
+
"""
|
|
13
|
+
Branding configuration for IAToolkit
|
|
14
|
+
"""
|
|
15
|
+
@inject
|
|
16
|
+
def __init__(self, config_service: ConfigurationService):
|
|
17
|
+
self.config_service = config_service
|
|
18
|
+
"""
|
|
19
|
+
Define los estilos de branding por defecto para la aplicación.
|
|
20
|
+
"""
|
|
21
|
+
self._default_branding = {
|
|
22
|
+
# --- Estilos del Encabezado Principal ---
|
|
23
|
+
"header_background_color": "#FFFFFF",
|
|
24
|
+
"header_text_color": "#6C757D",
|
|
25
|
+
"primary_font_weight": "600",
|
|
26
|
+
"primary_font_size": "1.2rem",
|
|
27
|
+
"secondary_font_weight": "400",
|
|
28
|
+
"secondary_font_size": "0.9rem",
|
|
29
|
+
"tertiary_font_weight": "300",
|
|
30
|
+
"tertiary_font_size": "0.8rem",
|
|
31
|
+
"tertiary_opacity": "0.7",
|
|
32
|
+
|
|
33
|
+
# headings
|
|
34
|
+
"brand_text_heading_color": "#334155", # Gris pizarra por defecto
|
|
35
|
+
|
|
36
|
+
# Estilos Globales de la Marca ---
|
|
37
|
+
"brand_primary_color": "#0d6efd", # Azul de Bootstrap por defecto
|
|
38
|
+
"brand_secondary_color": "#6c757d", # Gris de Bootstrap por defecto
|
|
39
|
+
"brand_text_on_primary": "#FFFFFF", # Texto blanco sobre color primario
|
|
40
|
+
"brand_text_on_secondary": "#FFFFFF", # Texto blanco sobre color secundario
|
|
41
|
+
|
|
42
|
+
# Estilos para Alertas de Error ---
|
|
43
|
+
"brand_danger_color": "#dc3545", # Rojo principal para alertas
|
|
44
|
+
"brand_danger_bg": "#f8d7da", # Fondo rojo pálido
|
|
45
|
+
"brand_danger_text": "#000000",
|
|
46
|
+
"brand_danger_border": "#f5c2c7", # Borde rojo intermedio
|
|
47
|
+
|
|
48
|
+
# Estilos para Alertas Informativas ---
|
|
49
|
+
"brand_info_bg": "#F0F4F8", # Un fondo de gris azulado muy pálido
|
|
50
|
+
"brand_info_text": "#0d6efd", # Texto en el color primario
|
|
51
|
+
"brand_info_border": "#D9E2EC", # Borde de gris azulado pálido
|
|
52
|
+
|
|
53
|
+
# Estilos para el Asistente de Prompts ---
|
|
54
|
+
"prompt_assistant_bg": "#f8f9fa",
|
|
55
|
+
"prompt_assistant_border": "#dee2e6",
|
|
56
|
+
"prompt_assistant_button_bg": "#FFFFFF",
|
|
57
|
+
"prompt_assistant_button_text": "#495057",
|
|
58
|
+
"prompt_assistant_button_border": "#ced4da",
|
|
59
|
+
"prompt_assistant_dropdown_bg": "#f8f9fa",
|
|
60
|
+
"prompt_assistant_header_bg": "#e9ecef",
|
|
61
|
+
"prompt_assistant_header_text": "#495057",
|
|
62
|
+
|
|
63
|
+
# this use the primary by default
|
|
64
|
+
"prompt_assistant_icon_color": None,
|
|
65
|
+
"prompt_assistant_item_hover_bg": None,
|
|
66
|
+
"prompt_assistant_item_hover_text": None,
|
|
67
|
+
|
|
68
|
+
# Color para el botón de Enviar ---
|
|
69
|
+
"send_button_color": "#212529" # Gris oscuro/casi negro por defecto
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def get_company_branding(self, company_short_name: str) -> dict:
|
|
73
|
+
"""
|
|
74
|
+
Retorna los estilos de branding finales para una compañía,
|
|
75
|
+
fusionando los valores por defecto con los personalizados.
|
|
76
|
+
"""
|
|
77
|
+
final_branding_values = self._default_branding.copy()
|
|
78
|
+
branding_data = self.config_service.get_configuration(company_short_name, 'branding')
|
|
79
|
+
final_branding_values.update(branding_data)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Función para convertir HEX a RGB
|
|
83
|
+
def hex_to_rgb(hex_color):
|
|
84
|
+
hex_color = hex_color.lstrip('#')
|
|
85
|
+
return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
|
|
86
|
+
|
|
87
|
+
primary_rgb = hex_to_rgb(final_branding_values['brand_primary_color'])
|
|
88
|
+
secondary_rgb = hex_to_rgb(final_branding_values['brand_secondary_color'])
|
|
89
|
+
|
|
90
|
+
# --- CONSTRUCCIÓN DE ESTILOS Y VARIABLES CSS ---
|
|
91
|
+
primary_text_style = (
|
|
92
|
+
f"font-weight: {final_branding_values['primary_font_weight']}; "
|
|
93
|
+
f"font-size: {final_branding_values['primary_font_size']};"
|
|
94
|
+
)
|
|
95
|
+
secondary_text_style = (
|
|
96
|
+
f"font-weight: {final_branding_values['secondary_font_weight']}; "
|
|
97
|
+
f"font-size: {final_branding_values['secondary_font_size']};"
|
|
98
|
+
)
|
|
99
|
+
tertiary_text_style = (
|
|
100
|
+
f"font-weight: {final_branding_values['tertiary_font_weight']}; "
|
|
101
|
+
f"font-size: {final_branding_values['tertiary_font_size']}; "
|
|
102
|
+
f"opacity: {final_branding_values['tertiary_opacity']};"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Generamos el bloque de variables CSS
|
|
106
|
+
css_variables = f"""
|
|
107
|
+
:root {{
|
|
108
|
+
--brand-primary-color: {final_branding_values['brand_primary_color']};
|
|
109
|
+
--brand-secondary-color: {final_branding_values['brand_secondary_color']};
|
|
110
|
+
--brand-header-bg: {final_branding_values['header_background_color']};
|
|
111
|
+
--brand-header-text: {final_branding_values['header_text_color']};
|
|
112
|
+
--brand-text-heading-color: {final_branding_values['brand_text_heading_color']};
|
|
113
|
+
|
|
114
|
+
--brand-primary-color-rgb: {', '.join(map(str, primary_rgb))};
|
|
115
|
+
--brand-secondary-color-rgb: {', '.join(map(str, secondary_rgb))};
|
|
116
|
+
--brand-text-on-primary: {final_branding_values['brand_text_on_primary']};
|
|
117
|
+
--brand-text-on-secondary: {final_branding_values['brand_text_on_secondary']};
|
|
118
|
+
--brand-modal-header-bg: {final_branding_values['header_background_color']};
|
|
119
|
+
--brand-modal-header-text: {final_branding_values['header_text_color']};
|
|
120
|
+
--brand-danger-color: {final_branding_values['brand_danger_color']};
|
|
121
|
+
--brand-danger-bg: {final_branding_values['brand_danger_bg']};
|
|
122
|
+
--brand-danger-text: {final_branding_values['brand_danger_text']};
|
|
123
|
+
--brand-danger-border: {final_branding_values['brand_danger_border']};
|
|
124
|
+
--brand-info-bg: {final_branding_values['brand_info_bg']};
|
|
125
|
+
--brand-info-text: {final_branding_values['brand_info_text'] or final_branding_values['brand_primary_color']};
|
|
126
|
+
--brand-info-border: {final_branding_values['brand_info_border']};
|
|
127
|
+
--brand-prompt-assistant-bg: {final_branding_values['prompt_assistant_bg']};
|
|
128
|
+
--brand-prompt-assistant-border: {final_branding_values['prompt_assistant_border']};
|
|
129
|
+
--brand-prompt-assistant-icon-color: {final_branding_values['prompt_assistant_icon_color'] or final_branding_values['brand_primary_color']};
|
|
130
|
+
--brand-prompt-assistant-button-bg: {final_branding_values['prompt_assistant_button_bg']};
|
|
131
|
+
--brand-prompt-assistant-button-text: {final_branding_values['prompt_assistant_button_text']};
|
|
132
|
+
--brand-prompt-assistant-button-border: {final_branding_values['prompt_assistant_button_border']};
|
|
133
|
+
--brand-prompt-assistant-dropdown-bg: {final_branding_values['prompt_assistant_dropdown_bg']};
|
|
134
|
+
--brand-prompt-assistant-header-bg: {final_branding_values['prompt_assistant_header_bg']};
|
|
135
|
+
--brand-prompt-assistant-header-text: {final_branding_values['prompt_assistant_header_text']};
|
|
136
|
+
--brand-prompt-assistant-item-hover-bg: {final_branding_values['prompt_assistant_item_hover_bg'] or final_branding_values['brand_primary_color']};
|
|
137
|
+
--brand-prompt-assistant-item-hover-text: {final_branding_values['prompt_assistant_item_hover_text'] or final_branding_values['brand_text_on_primary']};
|
|
138
|
+
|
|
139
|
+
}}
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
# get the company name from configuration for the branding render
|
|
143
|
+
company_name = self.config_service.get_configuration(company_short_name, 'name')
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
"name": company_name,
|
|
147
|
+
"primary_text_style": primary_text_style,
|
|
148
|
+
"secondary_text_style": secondary_text_style,
|
|
149
|
+
"tertiary_text_style": tertiary_text_style,
|
|
150
|
+
"header_text_color": final_branding_values['header_text_color'],
|
|
151
|
+
"css_variables": css_variables,
|
|
152
|
+
"send_button_color": final_branding_values['brand_primary_color']
|
|
153
|
+
}
|