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.
Files changed (122) hide show
  1. iatoolkit/__init__.py +2 -6
  2. iatoolkit/base_company.py +9 -29
  3. iatoolkit/cli_commands.py +1 -1
  4. iatoolkit/common/routes.py +96 -52
  5. iatoolkit/common/session_manager.py +2 -1
  6. iatoolkit/common/util.py +17 -27
  7. iatoolkit/company_registry.py +1 -2
  8. iatoolkit/iatoolkit.py +97 -53
  9. iatoolkit/infra/llm_client.py +15 -20
  10. iatoolkit/infra/llm_proxy.py +38 -10
  11. iatoolkit/infra/openai_adapter.py +1 -1
  12. iatoolkit/infra/redis_session_manager.py +48 -2
  13. iatoolkit/locales/en.yaml +167 -0
  14. iatoolkit/locales/es.yaml +163 -0
  15. iatoolkit/repositories/database_manager.py +23 -3
  16. iatoolkit/repositories/document_repo.py +1 -1
  17. iatoolkit/repositories/models.py +35 -10
  18. iatoolkit/repositories/profile_repo.py +3 -2
  19. iatoolkit/repositories/vs_repo.py +26 -20
  20. iatoolkit/services/auth_service.py +193 -0
  21. iatoolkit/services/branding_service.py +70 -25
  22. iatoolkit/services/company_context_service.py +155 -0
  23. iatoolkit/services/configuration_service.py +133 -0
  24. iatoolkit/services/dispatcher_service.py +80 -105
  25. iatoolkit/services/document_service.py +5 -2
  26. iatoolkit/services/embedding_service.py +146 -0
  27. iatoolkit/services/excel_service.py +30 -26
  28. iatoolkit/services/file_processor_service.py +4 -12
  29. iatoolkit/services/history_service.py +7 -16
  30. iatoolkit/services/i18n_service.py +104 -0
  31. iatoolkit/services/jwt_service.py +18 -29
  32. iatoolkit/services/language_service.py +83 -0
  33. iatoolkit/services/load_documents_service.py +100 -113
  34. iatoolkit/services/mail_service.py +9 -4
  35. iatoolkit/services/profile_service.py +152 -76
  36. iatoolkit/services/prompt_manager_service.py +20 -16
  37. iatoolkit/services/query_service.py +208 -96
  38. iatoolkit/services/search_service.py +11 -4
  39. iatoolkit/services/sql_service.py +57 -25
  40. iatoolkit/services/tasks_service.py +1 -1
  41. iatoolkit/services/user_feedback_service.py +72 -34
  42. iatoolkit/services/user_session_context_service.py +112 -54
  43. iatoolkit/static/images/fernando.jpeg +0 -0
  44. iatoolkit/static/js/chat_feedback_button.js +80 -0
  45. iatoolkit/static/js/chat_help_content.js +124 -0
  46. iatoolkit/static/js/chat_history_button.js +110 -0
  47. iatoolkit/static/js/chat_logout_button.js +36 -0
  48. iatoolkit/static/js/chat_main.js +135 -222
  49. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  50. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  51. iatoolkit/static/js/chat_reload_button.js +35 -0
  52. iatoolkit/static/styles/chat_iatoolkit.css +289 -210
  53. iatoolkit/static/styles/chat_modal.css +63 -77
  54. iatoolkit/static/styles/chat_public.css +107 -0
  55. iatoolkit/static/styles/landing_page.css +182 -0
  56. iatoolkit/static/styles/onboarding.css +176 -0
  57. iatoolkit/system_prompts/query_main.prompt +5 -22
  58. iatoolkit/templates/_company_header.html +20 -0
  59. iatoolkit/templates/_login_widget.html +42 -0
  60. iatoolkit/templates/base.html +40 -20
  61. iatoolkit/templates/change_password.html +57 -36
  62. iatoolkit/templates/chat.html +180 -86
  63. iatoolkit/templates/chat_modals.html +138 -68
  64. iatoolkit/templates/error.html +44 -8
  65. iatoolkit/templates/forgot_password.html +40 -23
  66. iatoolkit/templates/index.html +145 -0
  67. iatoolkit/templates/login_simulation.html +45 -0
  68. iatoolkit/templates/onboarding_shell.html +107 -0
  69. iatoolkit/templates/signup.html +63 -65
  70. iatoolkit/views/base_login_view.py +91 -0
  71. iatoolkit/views/change_password_view.py +56 -31
  72. iatoolkit/views/embedding_api_view.py +65 -0
  73. iatoolkit/views/external_login_view.py +61 -28
  74. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +10 -3
  75. iatoolkit/views/forgot_password_view.py +27 -21
  76. iatoolkit/views/help_content_api_view.py +54 -0
  77. iatoolkit/views/history_api_view.py +56 -0
  78. iatoolkit/views/home_view.py +50 -23
  79. iatoolkit/views/index_view.py +14 -0
  80. iatoolkit/views/init_context_api_view.py +74 -0
  81. iatoolkit/views/llmquery_api_view.py +58 -0
  82. iatoolkit/views/login_simulation_view.py +93 -0
  83. iatoolkit/views/login_view.py +130 -37
  84. iatoolkit/views/logout_api_view.py +49 -0
  85. iatoolkit/views/profile_api_view.py +46 -0
  86. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
  87. iatoolkit/views/signup_view.py +41 -36
  88. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  89. iatoolkit/views/tasks_review_api_view.py +55 -0
  90. iatoolkit/views/user_feedback_api_view.py +60 -0
  91. iatoolkit/views/verify_user_view.py +34 -29
  92. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +41 -23
  93. iatoolkit-0.71.2.dist-info/RECORD +122 -0
  94. iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
  95. iatoolkit/common/auth.py +0 -200
  96. iatoolkit/static/images/arrow_up.png +0 -0
  97. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  98. iatoolkit/static/images/logo_clinica.png +0 -0
  99. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  100. iatoolkit/static/images/logo_maxxa.png +0 -0
  101. iatoolkit/static/images/logo_notaria.png +0 -0
  102. iatoolkit/static/images/logo_tarjeta.png +0 -0
  103. iatoolkit/static/images/logo_umayor.png +0 -0
  104. iatoolkit/static/images/upload.png +0 -0
  105. iatoolkit/static/js/chat_feedback.js +0 -115
  106. iatoolkit/static/js/chat_history.js +0 -117
  107. iatoolkit/static/styles/chat_info.css +0 -53
  108. iatoolkit/templates/header.html +0 -31
  109. iatoolkit/templates/home.html +0 -199
  110. iatoolkit/templates/login.html +0 -43
  111. iatoolkit/templates/test.html +0 -9
  112. iatoolkit/views/chat_token_request_view.py +0 -98
  113. iatoolkit/views/chat_view.py +0 -58
  114. iatoolkit/views/download_file_view.py +0 -58
  115. iatoolkit/views/external_chat_login_view.py +0 -95
  116. iatoolkit/views/history_view.py +0 -57
  117. iatoolkit/views/llmquery_view.py +0 -65
  118. iatoolkit/views/tasks_review_view.py +0 -83
  119. iatoolkit/views/user_feedback_view.py +0 -74
  120. iatoolkit-0.11.0.dist-info/RECORD +0 -110
  121. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
  122. {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
- from datetime import datetime, timezone
19
- from iatoolkit.services.user_session_context_service import UserSessionContextService
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
- query_service: QueryService,
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 exits
41
+ # check if user exists
40
42
  user = self.profile_repo.get_user_by_email(email)
41
43
  if not user:
42
- return {"error": "Usuario no encontrado"}
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 {"error": "Contraseña inválida"}
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 {"error": "Empresa no encontrada"}
52
+ return {'success': False, "message": "missing company"}
51
53
 
52
- # check that user belongs to company
54
+ # check that user belongs to company
53
55
  if company not in user.companies:
54
- return {"error": "Usuario no esta autorizado para esta empresa"}
56
+ return {'success': False, "message": self.i18n_service.t('errors.services.user_not_authorized')}
55
57
 
56
58
  if not user.verified:
57
- return {"error": "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."}
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
- # clear history save user data into session manager
60
- self.set_user_session(user=user, company=company)
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
- # initialize company context
63
- self.query_service.llm_init_context(
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
- return {"message": "Login exitoso"}
69
- except Exception as e:
70
- logging.exception(f"login error: {str(e)}")
71
- return {"error": str(e)}
72
-
73
- def set_user_session(self, user: User, company: Company):
74
- SessionManager.set('user_id', user.id)
75
- SessionManager.set('company_id', company.id)
76
- SessionManager.set('company_short_name', company.short_name)
77
-
78
- # save user data into session manager
79
- user_data = {
80
- "id": user.id,
81
- "email": user.email,
82
- "user_fullname": f'{user.first_name} {user.last_name}',
83
- "company_id": company.id,
84
- "company": company.name,
85
- "company_short_name": company.short_name,
86
- "user_is_local": True, # origin of data
87
- "extras": {} # company specific data
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
- # save time session was activated (in timestamp format)
92
- SessionManager.set('last_activity', datetime.now(timezone.utc).timestamp())
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 {"error": f"la empresa {company_short_name} no existe"}
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": "La contraseña es incorrecta. No se puede agregar a la nueva empresa."}
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": "Usuario ya registrado en esta empresa"}
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": "Usuario asociado a nueva empresa"}
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": "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."}
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
- return {"error": message}
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": "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."}
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": "El usuario no existe."}
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": "Tu cuenta ha sido verificada exitosamente. Ahora puedes iniciar sesión."}
250
+ return {"message": self.i18n_service.t('flash_messages.account_verified_success')}
174
251
 
175
252
  except Exception as e:
176
- return {"error": str(e)}
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": "Las contraseñas no coinciden. Por favor, inténtalo nuevamente."}
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": "El código temporal no es válido. Por favor, verifica o solicita uno nuevo."}
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": "La clave se cambio correctamente"}
274
+ return {"message": self.i18n_service.t('flash_messages.password_changed_success')}
198
275
  except Exception as e:
199
- return {"error": str(e)}
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": "El usuario no existe."}
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": "se envio mail para cambio de clave"}
292
+ return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
216
293
  except Exception as e:
217
- return {"error": str(e)}
294
+ return {"error": self.i18n_service.t('errors.general.unexpected_error')}
218
295
 
219
296
  def validate_password(self, password):
220
297
  """
221
- Valida que una contraseña cumpla con los siguientes requisitos:
222
- - Al menos 8 caracteres de longitud
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, "La contraseña debe tener al menos 8 caracteres."
302
+ return False, "errors.validation.password_too_short"
230
303
 
231
304
  if not any(char.isupper() for char in password):
232
- return False, "La contraseña debe tener al menos una letra mayúscula."
305
+ return False, "errors.validation.password_no_uppercase"
233
306
 
234
307
  if not any(char.islower() for char in password):
235
- return False, "La contraseña debe tener al menos una letra minúscula."
308
+ return False, "errors.validation.password_no_lowercase"
236
309
 
237
310
  if not any(char.isdigit() for char in password):
238
- return False, "La contraseña debe tener al menos un número."
311
+ return False, "errors.validation.password_no_digit"
239
312
 
240
313
  if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
241
- return False, "La contraseña debe tener al menos un carácter especial."
314
+ return False, "errors.validation.password_no_special_char"
242
315
 
243
- return True, "La contraseña es válida."
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": f"la empresa {company_short_name} no existe"}
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, llm_query_repo: LLMQueryRepo, profile_repo: ProfileRepo):
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'No existe el archivo de prompt de sistemas: {prompt_filename}')
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'No existe el archivo de prompt: {relative_prompt_path}')
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'El campo custom_fields debe contener los campos: data_key y label')
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"No se encontró el prompt '{prompt_name}' para la empresa '{company.short_name}'")
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"El archivo para el prompt '{prompt_name}' no existe: {absolute_filepath}")
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"Error leyendo el archivo de prompt '{prompt_name}' en {absolute_filepath}: {e}")
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"Error al obtener el contenido del prompt para la empresa '{company.short_name}' y prompt '{prompt_name}': {e}")
112
+ f"error loading prompt '{prompt_name}' content for '{company.short_name}': {e}")
109
113
  raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
110
- f'Error al obtener el contenido del prompt "{prompt_name}" para la empresa {company.short_name}: {str(e)}')
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"El archivo para el prompt de sistema no existe en el paquete: {prompt.filename}")
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"Error leyendo el archivo de prompt del sistema '{prompt.filename}': {e}")
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'Error al obtener el contenido de los prompts de sistema": {str(e)}')
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 {'error': f'No existe la empresa: {company_short_name}'}
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"Error en get_prompts: {e}")
190
+ logging.error(f"error in get_prompts: {e}")
187
191
  return {'error': str(e)}
188
192