iatoolkit 0.11.0__py3-none-any.whl → 0.71.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 +2 -6
- iatoolkit/base_company.py +9 -29
- iatoolkit/cli_commands.py +1 -1
- iatoolkit/common/routes.py +96 -52
- iatoolkit/common/session_manager.py +2 -1
- iatoolkit/common/util.py +17 -27
- iatoolkit/company_registry.py +1 -2
- iatoolkit/iatoolkit.py +97 -53
- iatoolkit/infra/llm_client.py +15 -20
- iatoolkit/infra/llm_proxy.py +38 -10
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/locales/en.yaml +167 -0
- iatoolkit/locales/es.yaml +163 -0
- iatoolkit/repositories/database_manager.py +23 -3
- iatoolkit/repositories/document_repo.py +1 -1
- iatoolkit/repositories/models.py +35 -10
- iatoolkit/repositories/profile_repo.py +3 -2
- iatoolkit/repositories/vs_repo.py +26 -20
- iatoolkit/services/auth_service.py +193 -0
- iatoolkit/services/branding_service.py +70 -25
- iatoolkit/services/company_context_service.py +155 -0
- iatoolkit/services/configuration_service.py +133 -0
- iatoolkit/services/dispatcher_service.py +80 -105
- iatoolkit/services/document_service.py +5 -2
- iatoolkit/services/embedding_service.py +146 -0
- iatoolkit/services/excel_service.py +30 -26
- iatoolkit/services/file_processor_service.py +4 -12
- iatoolkit/services/history_service.py +7 -16
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +18 -29
- iatoolkit/services/language_service.py +83 -0
- iatoolkit/services/load_documents_service.py +100 -113
- iatoolkit/services/mail_service.py +9 -4
- iatoolkit/services/profile_service.py +152 -76
- iatoolkit/services/prompt_manager_service.py +20 -16
- iatoolkit/services/query_service.py +208 -96
- iatoolkit/services/search_service.py +11 -4
- iatoolkit/services/sql_service.py +57 -25
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +72 -34
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -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 +135 -222
- 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 +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +289 -210
- iatoolkit/static/styles/chat_modal.css +63 -77
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +176 -0
- iatoolkit/system_prompts/query_main.prompt +5 -22
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +40 -20
- iatoolkit/templates/change_password.html +57 -36
- iatoolkit/templates/chat.html +180 -86
- iatoolkit/templates/chat_modals.html +138 -68
- iatoolkit/templates/error.html +44 -8
- iatoolkit/templates/forgot_password.html +40 -23
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +45 -0
- iatoolkit/templates/onboarding_shell.html +107 -0
- iatoolkit/templates/signup.html +63 -65
- iatoolkit/views/base_login_view.py +91 -0
- iatoolkit/views/change_password_view.py +56 -31
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +10 -3
- iatoolkit/views/forgot_password_view.py +27 -21
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +50 -23
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +74 -0
- iatoolkit/views/llmquery_api_view.py +58 -0
- iatoolkit/views/login_simulation_view.py +93 -0
- iatoolkit/views/login_view.py +130 -37
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
- iatoolkit/views/signup_view.py +41 -36
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +34 -29
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +41 -23
- iatoolkit-0.71.2.dist-info/RECORD +122 -0
- iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_feedback.js +0 -115
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/static/styles/chat_info.css +0 -53
- iatoolkit/templates/header.html +0 -31
- iatoolkit/templates/home.html +0 -199
- iatoolkit/templates/login.html +0 -43
- iatoolkit/templates/test.html +0 -9
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -58
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -95
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit/views/user_feedback_view.py +0 -74
- iatoolkit-0.11.0.dist-info/RECORD +0 -110
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
|
@@ -5,91 +5,166 @@
|
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
8
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
8
9
|
from iatoolkit.repositories.models import User, Company, ApiKey
|
|
9
10
|
from flask_bcrypt import check_password_hash
|
|
10
11
|
from iatoolkit.common.session_manager import SessionManager
|
|
12
|
+
from iatoolkit.services.user_session_context_service import UserSessionContextService
|
|
11
13
|
from flask_bcrypt import Bcrypt
|
|
12
14
|
from iatoolkit.infra.mail_app import MailApp
|
|
13
15
|
import random
|
|
14
|
-
import logging
|
|
15
16
|
import re
|
|
16
17
|
import secrets
|
|
17
18
|
import string
|
|
18
|
-
|
|
19
|
-
from iatoolkit.services.
|
|
20
|
-
from iatoolkit.services.query_service import QueryService
|
|
19
|
+
import logging
|
|
20
|
+
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class ProfileService:
|
|
24
24
|
@inject
|
|
25
25
|
def __init__(self,
|
|
26
|
+
i18n_service: I18nService,
|
|
26
27
|
profile_repo: ProfileRepo,
|
|
27
28
|
session_context_service: UserSessionContextService,
|
|
28
|
-
|
|
29
|
+
dispatcher: Dispatcher,
|
|
29
30
|
mail_app: MailApp):
|
|
31
|
+
self.i18n_service = i18n_service
|
|
30
32
|
self.profile_repo = profile_repo
|
|
33
|
+
self.dispatcher = dispatcher
|
|
31
34
|
self.session_context = session_context_service
|
|
32
|
-
self.query_service = query_service
|
|
33
35
|
self.mail_app = mail_app
|
|
34
36
|
self.bcrypt = Bcrypt()
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def login(self, company_short_name: str, email: str, password: str) -> dict:
|
|
38
40
|
try:
|
|
39
|
-
# check if
|
|
41
|
+
# check if user exists
|
|
40
42
|
user = self.profile_repo.get_user_by_email(email)
|
|
41
43
|
if not user:
|
|
42
|
-
return {
|
|
44
|
+
return {'success': False, 'message': self.i18n_service.t('errors.auth.user_not_found')}
|
|
43
45
|
|
|
44
46
|
# check the encrypted password
|
|
45
47
|
if not check_password_hash(user.password, password):
|
|
46
|
-
return {
|
|
48
|
+
return {'success': False, 'message': self.i18n_service.t('errors.auth.invalid_password')}
|
|
47
49
|
|
|
48
|
-
company = self.get_company_by_short_name(company_short_name)
|
|
50
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
49
51
|
if not company:
|
|
50
|
-
return {"
|
|
52
|
+
return {'success': False, "message": "missing company"}
|
|
51
53
|
|
|
52
|
-
# check that user belongs to
|
|
54
|
+
# check that user belongs to company
|
|
53
55
|
if company not in user.companies:
|
|
54
|
-
return {"
|
|
56
|
+
return {'success': False, "message": self.i18n_service.t('errors.services.user_not_authorized')}
|
|
55
57
|
|
|
56
58
|
if not user.verified:
|
|
57
|
-
return {
|
|
59
|
+
return {'success': False,
|
|
60
|
+
"message": self.i18n_service.t('errors.services.account_not_verified')}
|
|
61
|
+
|
|
62
|
+
# 1. Build the local user profile dictionary here.
|
|
63
|
+
# the user_profile variables are used on the LLM templates also (see in query_main.prompt)
|
|
64
|
+
user_identifier = user.email # no longer de ID
|
|
65
|
+
user_profile = {
|
|
66
|
+
"user_email": user.email,
|
|
67
|
+
"user_fullname": f'{user.first_name} {user.last_name}',
|
|
68
|
+
"user_is_local": True,
|
|
69
|
+
"extras": {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# 2. create user_profile in context
|
|
73
|
+
self.save_user_profile(company, user_identifier, user_profile)
|
|
74
|
+
|
|
75
|
+
# 3. create the web session
|
|
76
|
+
self.set_session_for_user(company.short_name, user_identifier)
|
|
77
|
+
return {'success': True, "user_identifier": user_identifier, "message": "Login ok"}
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logging.error(f"Error in login: {e}")
|
|
80
|
+
return {'success': False, "message": str(e)}
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
def create_external_user_profile_context(self, company: Company, user_identifier: str):
|
|
83
|
+
"""
|
|
84
|
+
Public method for views to create a user profile context for an external user.
|
|
85
|
+
"""
|
|
86
|
+
# 1. Fetch the external user profile via Dispatcher.
|
|
87
|
+
external_user_profile = self.dispatcher.get_user_info(
|
|
88
|
+
company_name=company.short_name,
|
|
89
|
+
user_identifier=user_identifier
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# 2. Call the session creation helper with external_user_id as user_identifier
|
|
93
|
+
self.save_user_profile(
|
|
94
|
+
company=company,
|
|
95
|
+
user_identifier=user_identifier,
|
|
96
|
+
user_profile=external_user_profile)
|
|
97
|
+
|
|
98
|
+
# 3. make sure the flask session is clean
|
|
99
|
+
SessionManager.clear()
|
|
100
|
+
|
|
101
|
+
def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
|
|
102
|
+
"""
|
|
103
|
+
Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
|
|
104
|
+
"""
|
|
105
|
+
user_profile['company_short_name'] = company.short_name
|
|
106
|
+
user_profile['user_identifier'] = user_identifier
|
|
107
|
+
user_profile['id'] = user_identifier
|
|
108
|
+
user_profile['company_id'] = company.id
|
|
109
|
+
user_profile['company'] = company.name
|
|
61
110
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
company_short_name=company_short_name,
|
|
65
|
-
local_user_id=user.id
|
|
66
|
-
)
|
|
111
|
+
# save user_profile in Redis session
|
|
112
|
+
self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
def set_session_for_user(self, company_short_name: str, user_identifier:str ):
|
|
115
|
+
# save a min Flask session cookie for this user
|
|
116
|
+
SessionManager.set('company_short_name', company_short_name)
|
|
117
|
+
SessionManager.set('user_identifier', user_identifier)
|
|
118
|
+
|
|
119
|
+
def get_current_session_info(self) -> dict:
|
|
120
|
+
"""
|
|
121
|
+
Gets the current web user's profile from the unified session.
|
|
122
|
+
This is the standard way to access user data for web requests.
|
|
123
|
+
"""
|
|
124
|
+
# 1. Get identifiers from the simple Flask session cookie.
|
|
125
|
+
user_identifier = SessionManager.get('user_identifier')
|
|
126
|
+
company_short_name = SessionManager.get('company_short_name')
|
|
127
|
+
|
|
128
|
+
if not user_identifier or not company_short_name:
|
|
129
|
+
# No authenticated web user.
|
|
130
|
+
return {}
|
|
131
|
+
|
|
132
|
+
# 2. Use the identifiers to fetch the full, authoritative profile from Redis.
|
|
133
|
+
profile = self.session_context.get_profile_data(company_short_name, user_identifier)
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"user_identifier": user_identifier,
|
|
137
|
+
"company_short_name": company_short_name,
|
|
138
|
+
"profile": profile
|
|
88
139
|
}
|
|
89
|
-
SessionManager.set('user', user_data)
|
|
90
140
|
|
|
91
|
-
|
|
92
|
-
|
|
141
|
+
def update_user_language(self, user_identifier: str, new_lang: str) -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Business logic to update a user's preferred language.
|
|
144
|
+
It validates the language and then calls the generic update method.
|
|
145
|
+
"""
|
|
146
|
+
# 1. Validate that the language is supported by checking the loaded translations.
|
|
147
|
+
if new_lang not in self.i18n_service.translations:
|
|
148
|
+
return {'success': False, 'error_message': self.i18n_service.t('errors.general.unsupported_language')}
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
# 2. Call the generic update_user method, passing the specific field to update.
|
|
152
|
+
self.update_user(user_identifier, preferred_language=new_lang)
|
|
153
|
+
return {'success': True, 'message': 'Language updated successfully.'}
|
|
154
|
+
except Exception as e:
|
|
155
|
+
# Log the error and return a generic failure message.
|
|
156
|
+
logging.error(f"Failed to update language for {user_identifier}: {e}")
|
|
157
|
+
return {'success': False, 'error_message': self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_profile_by_identifier(self, company_short_name: str, user_identifier: str) -> dict:
|
|
161
|
+
"""
|
|
162
|
+
Fetches a user profile directly by their identifier, bypassing the Flask session.
|
|
163
|
+
This is ideal for API-side checks.
|
|
164
|
+
"""
|
|
165
|
+
if not company_short_name or not user_identifier:
|
|
166
|
+
return {}
|
|
167
|
+
return self.session_context.get_profile_data(company_short_name, user_identifier)
|
|
93
168
|
|
|
94
169
|
|
|
95
170
|
def signup(self,
|
|
@@ -103,9 +178,10 @@ class ProfileService:
|
|
|
103
178
|
try:
|
|
104
179
|
|
|
105
180
|
# get company info
|
|
106
|
-
company = self.get_company_by_short_name(company_short_name)
|
|
181
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
107
182
|
if not company:
|
|
108
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
"error": self.i18n_service.t('errors.signup.company_not_found', company_name=company_short_name)}
|
|
109
185
|
|
|
110
186
|
# normalize format's
|
|
111
187
|
email = email.lower()
|
|
@@ -115,24 +191,25 @@ class ProfileService:
|
|
|
115
191
|
if existing_user:
|
|
116
192
|
# validate password
|
|
117
193
|
if not self.bcrypt.check_password_hash(existing_user.password, password):
|
|
118
|
-
return {"error":
|
|
194
|
+
return {"error": self.i18n_service.t('errors.signup.incorrect_password_for_existing_user', email=email)}
|
|
119
195
|
|
|
120
196
|
# check if register
|
|
121
197
|
if company in existing_user.companies:
|
|
122
|
-
return {"error":
|
|
198
|
+
return {"error": self.i18n_service.t('errors.signup.user_already_registered', email=email)}
|
|
123
199
|
else:
|
|
124
200
|
# add new company to existing user
|
|
125
201
|
existing_user.companies.append(company)
|
|
126
202
|
self.profile_repo.save_user(existing_user)
|
|
127
|
-
return {"message":
|
|
203
|
+
return {"message": self.i18n_service.t('flash_messages.user_associated_success')}
|
|
128
204
|
|
|
129
205
|
# add the new user
|
|
130
206
|
if password != confirm_password:
|
|
131
|
-
return {"error":
|
|
207
|
+
return {"error": self.i18n_service.t('errors.signup.password_mismatch')}
|
|
132
208
|
|
|
133
209
|
is_valid, message = self.validate_password(password)
|
|
134
210
|
if not is_valid:
|
|
135
|
-
|
|
211
|
+
# Translate the key returned by validate_password
|
|
212
|
+
return {"error": self.i18n_service.t(message)}
|
|
136
213
|
|
|
137
214
|
# encrypt the password
|
|
138
215
|
hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
|
|
@@ -154,9 +231,9 @@ class ProfileService:
|
|
|
154
231
|
# send email with verification
|
|
155
232
|
self.send_verification_email(new_user, company_short_name)
|
|
156
233
|
|
|
157
|
-
return {"message":
|
|
234
|
+
return {"message": self.i18n_service.t('flash_messages.signup_success')}
|
|
158
235
|
except Exception as e:
|
|
159
|
-
return {"error": str(e)}
|
|
236
|
+
return {"error": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
|
|
160
237
|
|
|
161
238
|
def update_user(self, email: str, **kwargs) -> User:
|
|
162
239
|
return self.profile_repo.update_user(email, **kwargs)
|
|
@@ -166,14 +243,14 @@ class ProfileService:
|
|
|
166
243
|
# check if user exist
|
|
167
244
|
user = self.profile_repo.get_user_by_email(email)
|
|
168
245
|
if not user:
|
|
169
|
-
return {"error":
|
|
246
|
+
return {"error": self.i18n_service.t('errors.verification.user_not_found')}
|
|
170
247
|
|
|
171
248
|
# activate the user account
|
|
172
249
|
self.profile_repo.verify_user(email)
|
|
173
|
-
return {"message":
|
|
250
|
+
return {"message": self.i18n_service.t('flash_messages.account_verified_success')}
|
|
174
251
|
|
|
175
252
|
except Exception as e:
|
|
176
|
-
return {"error":
|
|
253
|
+
return {"error": self.i18n_service.t('errors.general.unexpected_error')}
|
|
177
254
|
|
|
178
255
|
def change_password(self,
|
|
179
256
|
email: str,
|
|
@@ -182,28 +259,28 @@ class ProfileService:
|
|
|
182
259
|
confirm_password: str):
|
|
183
260
|
try:
|
|
184
261
|
if new_password != confirm_password:
|
|
185
|
-
return {"error":
|
|
262
|
+
return {"error": self.i18n_service.t('errors.change_password.password_mismatch')}
|
|
186
263
|
|
|
187
264
|
# check the temporary code
|
|
188
265
|
user = self.profile_repo.get_user_by_email(email)
|
|
189
266
|
if not user or user.temp_code != temp_code:
|
|
190
|
-
return {"error":
|
|
267
|
+
return {"error": self.i18n_service.t('errors.change_password.invalid_temp_code')}
|
|
191
268
|
|
|
192
269
|
# encrypt and save the password, make the temporary code invalid
|
|
193
270
|
hashed_password = self.bcrypt.generate_password_hash(new_password).decode('utf-8')
|
|
194
271
|
self.profile_repo.update_password(email, hashed_password)
|
|
195
272
|
self.profile_repo.reset_temp_code(email)
|
|
196
273
|
|
|
197
|
-
return {"message":
|
|
274
|
+
return {"message": self.i18n_service.t('flash_messages.password_changed_success')}
|
|
198
275
|
except Exception as e:
|
|
199
|
-
return {"error":
|
|
276
|
+
return {"error": self.i18n_service.t('errors.general.unexpected_error')}
|
|
200
277
|
|
|
201
278
|
def forgot_password(self, email: str, reset_url: str):
|
|
202
279
|
try:
|
|
203
280
|
# Verificar si el usuario existe
|
|
204
281
|
user = self.profile_repo.get_user_by_email(email)
|
|
205
282
|
if not user:
|
|
206
|
-
return {"error":
|
|
283
|
+
return {"error": self.i18n_service.t('errors.forgot_password.user_not_registered', email=email)}
|
|
207
284
|
|
|
208
285
|
# Gen a temporary code and store in the repositories
|
|
209
286
|
temp_code = ''.join(random.choices(string.ascii_letters + string.digits, k=6)).upper()
|
|
@@ -212,35 +289,31 @@ class ProfileService:
|
|
|
212
289
|
# send email to the user
|
|
213
290
|
self.send_forgot_password_email(user, reset_url)
|
|
214
291
|
|
|
215
|
-
return {"message":
|
|
292
|
+
return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
|
|
216
293
|
except Exception as e:
|
|
217
|
-
return {"error":
|
|
294
|
+
return {"error": self.i18n_service.t('errors.general.unexpected_error')}
|
|
218
295
|
|
|
219
296
|
def validate_password(self, password):
|
|
220
297
|
"""
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
- Contiene al menos una letra mayúscula
|
|
224
|
-
- Contiene al menos una letra minúscula
|
|
225
|
-
- Contiene al menos un número
|
|
226
|
-
- Contiene al menos un carácter especial
|
|
298
|
+
Validates that a password meets all requirements.
|
|
299
|
+
Returns (True, "...") on success, or (False, "translation.key") on failure.
|
|
227
300
|
"""
|
|
228
301
|
if len(password) < 8:
|
|
229
|
-
return False, "
|
|
302
|
+
return False, "errors.validation.password_too_short"
|
|
230
303
|
|
|
231
304
|
if not any(char.isupper() for char in password):
|
|
232
|
-
return False, "
|
|
305
|
+
return False, "errors.validation.password_no_uppercase"
|
|
233
306
|
|
|
234
307
|
if not any(char.islower() for char in password):
|
|
235
|
-
return False, "
|
|
308
|
+
return False, "errors.validation.password_no_lowercase"
|
|
236
309
|
|
|
237
310
|
if not any(char.isdigit() for char in password):
|
|
238
|
-
return False, "
|
|
311
|
+
return False, "errors.validation.password_no_digit"
|
|
239
312
|
|
|
240
313
|
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
|
241
|
-
return False, "
|
|
314
|
+
return False, "errors.validation.password_no_special_char"
|
|
242
315
|
|
|
243
|
-
return True, "
|
|
316
|
+
return True, "Password is valid."
|
|
244
317
|
|
|
245
318
|
def get_companies(self):
|
|
246
319
|
return self.profile_repo.get_companies()
|
|
@@ -248,10 +321,13 @@ class ProfileService:
|
|
|
248
321
|
def get_company_by_short_name(self, short_name: str) -> Company:
|
|
249
322
|
return self.profile_repo.get_company_by_short_name(short_name)
|
|
250
323
|
|
|
324
|
+
def get_active_api_key_entry(self, api_key_value: str) -> ApiKey | None:
|
|
325
|
+
return self.profile_repo.get_active_api_key_entry(api_key_value)
|
|
326
|
+
|
|
251
327
|
def new_api_key(self, company_short_name: str):
|
|
252
328
|
company = self.get_company_by_short_name(company_short_name)
|
|
253
329
|
if not company:
|
|
254
|
-
return {"error":
|
|
330
|
+
return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
|
|
255
331
|
|
|
256
332
|
length = 40 # lenght of the api key
|
|
257
333
|
alphabet = string.ascii_letters + string.digits
|
|
@@ -5,21 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
|
-
|
|
9
|
-
import logging
|
|
8
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
10
9
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
11
10
|
from collections import defaultdict
|
|
12
11
|
from iatoolkit.repositories.models import Prompt, PromptCategory, Company
|
|
13
12
|
import os
|
|
14
13
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
15
14
|
import importlib.resources
|
|
15
|
+
import logging
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class PromptService:
|
|
19
19
|
@inject
|
|
20
|
-
def __init__(self,
|
|
20
|
+
def __init__(self,
|
|
21
|
+
llm_query_repo: LLMQueryRepo,
|
|
22
|
+
profile_repo: ProfileRepo,
|
|
23
|
+
i18n_service: I18nService):
|
|
21
24
|
self.llm_query_repo = llm_query_repo
|
|
22
25
|
self.profile_repo = profile_repo
|
|
26
|
+
self.i18n_service = i18n_service
|
|
23
27
|
|
|
24
28
|
def create_prompt(self,
|
|
25
29
|
prompt_name: str,
|
|
@@ -36,20 +40,20 @@ class PromptService:
|
|
|
36
40
|
if is_system_prompt:
|
|
37
41
|
if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
|
|
38
42
|
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
39
|
-
f'
|
|
43
|
+
f'missing system prompt file: {prompt_filename}')
|
|
40
44
|
else:
|
|
41
45
|
template_dir = f'companies/{company.short_name}/prompts'
|
|
42
46
|
|
|
43
47
|
relative_prompt_path = os.path.join(template_dir, prompt_filename)
|
|
44
48
|
if not os.path.exists(relative_prompt_path):
|
|
45
49
|
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
46
|
-
f'
|
|
50
|
+
f'missing prompt file: {relative_prompt_path}')
|
|
47
51
|
|
|
48
52
|
if custom_fields:
|
|
49
53
|
for f in custom_fields:
|
|
50
54
|
if ('data_key' not in f) or ('label' not in f):
|
|
51
55
|
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_PARAMETER,
|
|
52
|
-
f'
|
|
56
|
+
f'The field "custom_fields" must contain the following keys: data_key y label')
|
|
53
57
|
|
|
54
58
|
# add default value for data_type
|
|
55
59
|
if 'type' not in f:
|
|
@@ -82,20 +86,20 @@ class PromptService:
|
|
|
82
86
|
user_prompt = self.llm_query_repo.get_prompt_by_name(company, prompt_name)
|
|
83
87
|
if not user_prompt:
|
|
84
88
|
raise IAToolkitException(IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND,
|
|
85
|
-
f"
|
|
89
|
+
f"prompt not found '{prompt_name}' for company '{company.short_name}'")
|
|
86
90
|
|
|
87
91
|
prompt_file = f'companies/{company.short_name}/prompts/{user_prompt.filename}'
|
|
88
92
|
absolute_filepath = os.path.join(execution_dir, prompt_file)
|
|
89
93
|
if not os.path.exists(absolute_filepath):
|
|
90
94
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
91
|
-
f"
|
|
95
|
+
f"prompt file '{prompt_name}' does not exist: {absolute_filepath}")
|
|
92
96
|
|
|
93
97
|
try:
|
|
94
98
|
with open(absolute_filepath, 'r', encoding='utf-8') as f:
|
|
95
99
|
user_prompt_content = f.read()
|
|
96
100
|
except Exception as e:
|
|
97
101
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
98
|
-
f"
|
|
102
|
+
f"error while reading prompt: '{prompt_name}' in this pathname {absolute_filepath}: {e}")
|
|
99
103
|
|
|
100
104
|
return user_prompt_content
|
|
101
105
|
|
|
@@ -105,9 +109,9 @@ class PromptService:
|
|
|
105
109
|
raise
|
|
106
110
|
except Exception as e:
|
|
107
111
|
logging.exception(
|
|
108
|
-
f"
|
|
112
|
+
f"error loading prompt '{prompt_name}' content for '{company.short_name}': {e}")
|
|
109
113
|
raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
|
|
110
|
-
f'
|
|
114
|
+
f'error loading prompt "{prompt_name}" content for company {company.short_name}: {str(e)}')
|
|
111
115
|
|
|
112
116
|
def get_system_prompt(self):
|
|
113
117
|
try:
|
|
@@ -121,10 +125,10 @@ class PromptService:
|
|
|
121
125
|
content = importlib.resources.read_text('iatoolkit.system_prompts', prompt.filename)
|
|
122
126
|
system_prompt_content.append(content)
|
|
123
127
|
except FileNotFoundError:
|
|
124
|
-
logging.warning(f"
|
|
128
|
+
logging.warning(f"Prompt file does not exist in the package: {prompt.filename}")
|
|
125
129
|
except Exception as e:
|
|
126
130
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
127
|
-
f"
|
|
131
|
+
f"error reading system prompt '{prompt.filename}': {e}")
|
|
128
132
|
|
|
129
133
|
# join the system prompts into a single string
|
|
130
134
|
return "\n".join(system_prompt_content)
|
|
@@ -135,14 +139,14 @@ class PromptService:
|
|
|
135
139
|
logging.exception(
|
|
136
140
|
f"Error al obtener el contenido del prompt de sistema: {e}")
|
|
137
141
|
raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
|
|
138
|
-
f'
|
|
142
|
+
f'error reading the system prompts": {str(e)}')
|
|
139
143
|
|
|
140
144
|
def get_user_prompts(self, company_short_name: str) -> dict:
|
|
141
145
|
try:
|
|
142
146
|
# validate company
|
|
143
147
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
144
148
|
if not company:
|
|
145
|
-
return {
|
|
149
|
+
return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
|
|
146
150
|
|
|
147
151
|
# get all the prompts
|
|
148
152
|
all_prompts = self.llm_query_repo.get_prompts(company)
|
|
@@ -183,6 +187,6 @@ class PromptService:
|
|
|
183
187
|
return {'message': categorized_prompts}
|
|
184
188
|
|
|
185
189
|
except Exception as e:
|
|
186
|
-
logging.error(f"
|
|
190
|
+
logging.error(f"error in get_prompts: {e}")
|
|
187
191
|
return {'error': str(e)}
|
|
188
192
|
|