iatoolkit 0.71.4__py3-none-any.whl → 1.4.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 (114) hide show
  1. iatoolkit/__init__.py +19 -7
  2. iatoolkit/base_company.py +1 -71
  3. iatoolkit/cli_commands.py +9 -21
  4. iatoolkit/common/exceptions.py +2 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +38 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +53 -32
  10. iatoolkit/common/util.py +17 -12
  11. iatoolkit/company_registry.py +55 -14
  12. iatoolkit/{iatoolkit.py → core.py} +102 -72
  13. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  14. iatoolkit/infra/llm_providers/__init__.py +0 -0
  15. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  16. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  17. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  18. iatoolkit/infra/llm_proxy.py +235 -134
  19. iatoolkit/infra/llm_response.py +5 -0
  20. iatoolkit/locales/en.yaml +134 -4
  21. iatoolkit/locales/es.yaml +293 -162
  22. iatoolkit/repositories/database_manager.py +92 -22
  23. iatoolkit/repositories/document_repo.py +7 -0
  24. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  25. iatoolkit/repositories/llm_query_repo.py +36 -22
  26. iatoolkit/repositories/models.py +86 -95
  27. iatoolkit/repositories/profile_repo.py +64 -13
  28. iatoolkit/repositories/vs_repo.py +31 -28
  29. iatoolkit/services/auth_service.py +1 -1
  30. iatoolkit/services/branding_service.py +1 -1
  31. iatoolkit/services/company_context_service.py +96 -39
  32. iatoolkit/services/configuration_service.py +329 -67
  33. iatoolkit/services/dispatcher_service.py +51 -227
  34. iatoolkit/services/document_service.py +10 -1
  35. iatoolkit/services/embedding_service.py +9 -6
  36. iatoolkit/services/excel_service.py +50 -2
  37. iatoolkit/services/file_processor_service.py +0 -5
  38. iatoolkit/services/history_manager_service.py +208 -0
  39. iatoolkit/services/jwt_service.py +1 -1
  40. iatoolkit/services/knowledge_base_service.py +412 -0
  41. iatoolkit/services/language_service.py +8 -2
  42. iatoolkit/services/license_service.py +82 -0
  43. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
  44. iatoolkit/services/load_documents_service.py +18 -47
  45. iatoolkit/services/mail_service.py +171 -25
  46. iatoolkit/services/profile_service.py +69 -36
  47. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
  48. iatoolkit/services/query_service.py +229 -203
  49. iatoolkit/services/sql_service.py +116 -34
  50. iatoolkit/services/tool_service.py +246 -0
  51. iatoolkit/services/user_feedback_service.py +18 -6
  52. iatoolkit/services/user_session_context_service.py +121 -51
  53. iatoolkit/static/images/iatoolkit_core.png +0 -0
  54. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  55. iatoolkit/static/js/chat_feedback_button.js +1 -1
  56. iatoolkit/static/js/chat_help_content.js +4 -4
  57. iatoolkit/static/js/chat_main.js +61 -9
  58. iatoolkit/static/js/chat_model_selector.js +227 -0
  59. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  60. iatoolkit/static/js/chat_reload_button.js +4 -1
  61. iatoolkit/static/styles/chat_iatoolkit.css +59 -3
  62. iatoolkit/static/styles/chat_public.css +28 -0
  63. iatoolkit/static/styles/documents.css +598 -0
  64. iatoolkit/static/styles/landing_page.css +223 -7
  65. iatoolkit/static/styles/llm_output.css +34 -1
  66. iatoolkit/system_prompts/__init__.py +0 -0
  67. iatoolkit/system_prompts/query_main.prompt +28 -3
  68. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  69. iatoolkit/templates/_company_header.html +30 -5
  70. iatoolkit/templates/_login_widget.html +3 -3
  71. iatoolkit/templates/base.html +13 -0
  72. iatoolkit/templates/chat.html +45 -3
  73. iatoolkit/templates/forgot_password.html +3 -2
  74. iatoolkit/templates/onboarding_shell.html +1 -2
  75. iatoolkit/templates/signup.html +3 -0
  76. iatoolkit/views/base_login_view.py +8 -3
  77. iatoolkit/views/change_password_view.py +1 -1
  78. iatoolkit/views/chat_view.py +76 -0
  79. iatoolkit/views/forgot_password_view.py +9 -4
  80. iatoolkit/views/history_api_view.py +3 -3
  81. iatoolkit/views/home_view.py +4 -2
  82. iatoolkit/views/init_context_api_view.py +1 -1
  83. iatoolkit/views/llmquery_api_view.py +4 -3
  84. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  85. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
  86. iatoolkit/views/login_view.py +25 -8
  87. iatoolkit/views/logout_api_view.py +10 -2
  88. iatoolkit/views/prompt_api_view.py +1 -1
  89. iatoolkit/views/rag_api_view.py +216 -0
  90. iatoolkit/views/root_redirect_view.py +22 -0
  91. iatoolkit/views/signup_view.py +12 -4
  92. iatoolkit/views/static_page_view.py +27 -0
  93. iatoolkit/views/users_api_view.py +33 -0
  94. iatoolkit/views/verify_user_view.py +1 -1
  95. iatoolkit-1.4.2.dist-info/METADATA +268 -0
  96. iatoolkit-1.4.2.dist-info/RECORD +133 -0
  97. iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  98. iatoolkit/repositories/tasks_repo.py +0 -52
  99. iatoolkit/services/history_service.py +0 -37
  100. iatoolkit/services/search_service.py +0 -55
  101. iatoolkit/services/tasks_service.py +0 -188
  102. iatoolkit/templates/about.html +0 -13
  103. iatoolkit/templates/index.html +0 -145
  104. iatoolkit/templates/login_simulation.html +0 -45
  105. iatoolkit/views/external_login_view.py +0 -73
  106. iatoolkit/views/index_view.py +0 -14
  107. iatoolkit/views/login_simulation_view.py +0 -93
  108. iatoolkit/views/tasks_api_view.py +0 -72
  109. iatoolkit/views/tasks_review_api_view.py +0 -55
  110. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  111. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  112. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
  113. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
  114. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
@@ -9,15 +9,18 @@ from iatoolkit.services.i18n_service import I18nService
9
9
  from iatoolkit.repositories.models import User, Company, ApiKey
10
10
  from flask_bcrypt import check_password_hash
11
11
  from iatoolkit.common.session_manager import SessionManager
12
+ from iatoolkit.services.dispatcher_service import Dispatcher
13
+ from iatoolkit.services.language_service import LanguageService
12
14
  from iatoolkit.services.user_session_context_service import UserSessionContextService
15
+ from iatoolkit.services.configuration_service import ConfigurationService
13
16
  from flask_bcrypt import Bcrypt
14
- from iatoolkit.infra.mail_app import MailApp
17
+ from iatoolkit.services.mail_service import MailService
15
18
  import random
16
19
  import re
17
20
  import secrets
18
21
  import string
19
22
  import logging
20
- from iatoolkit.services.dispatcher_service import Dispatcher
23
+ from typing import List, Dict
21
24
 
22
25
 
23
26
  class ProfileService:
@@ -26,16 +29,19 @@ class ProfileService:
26
29
  i18n_service: I18nService,
27
30
  profile_repo: ProfileRepo,
28
31
  session_context_service: UserSessionContextService,
32
+ config_service: ConfigurationService,
33
+ lang_service: LanguageService,
29
34
  dispatcher: Dispatcher,
30
- mail_app: MailApp):
35
+ mail_service: MailService):
31
36
  self.i18n_service = i18n_service
32
37
  self.profile_repo = profile_repo
33
38
  self.dispatcher = dispatcher
34
39
  self.session_context = session_context_service
35
- self.mail_app = mail_app
40
+ self.config_service = config_service
41
+ self.lang_service = lang_service
42
+ self.mail_service = mail_service
36
43
  self.bcrypt = Bcrypt()
37
44
 
38
-
39
45
  def login(self, company_short_name: str, email: str, password: str) -> dict:
40
46
  try:
41
47
  # check if user exists
@@ -59,13 +65,17 @@ class ProfileService:
59
65
  return {'success': False,
60
66
  "message": self.i18n_service.t('errors.services.account_not_verified')}
61
67
 
68
+ user_role = self.profile_repo.get_user_role_in_company(company.id, user.id)
69
+
62
70
  # 1. Build the local user profile dictionary here.
63
71
  # 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
72
+ user_identifier = user.email
65
73
  user_profile = {
66
74
  "user_email": user.email,
67
75
  "user_fullname": f'{user.first_name} {user.last_name}',
68
76
  "user_is_local": True,
77
+ "user_id": user.id,
78
+ "user_role": user_role,
69
79
  "extras": {}
70
80
  }
71
81
 
@@ -79,25 +89,6 @@ class ProfileService:
79
89
  logging.error(f"Error in login: {e}")
80
90
  return {'success': False, "message": str(e)}
81
91
 
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
92
  def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
102
93
  """
103
94
  Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
@@ -107,6 +98,7 @@ class ProfileService:
107
98
  user_profile['id'] = user_identifier
108
99
  user_profile['company_id'] = company.id
109
100
  user_profile['company'] = company.name
101
+ user_profile['language'] = self.lang_service.get_current_language()
110
102
 
111
103
  # save user_profile in Redis session
112
104
  self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
@@ -214,24 +206,34 @@ class ProfileService:
214
206
  # encrypt the password
215
207
  hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
216
208
 
209
+ # account verification can be skiped with this security parameter
210
+ verified = False
211
+ cfg = self.config_service.get_configuration(company_short_name, 'parameters')
212
+ if cfg and not cfg.get('verify_account', True):
213
+ verified = True
214
+ message = self.i18n_service.t('flash_messages.signup_success_no_verification')
215
+
217
216
  # create the new user
218
217
  new_user = User(email=email,
219
218
  password=hashed_password,
220
219
  first_name=first_name.lower(),
221
220
  last_name=last_name.lower(),
222
- verified=False,
221
+ verified=verified,
223
222
  verification_url=verification_url
224
223
  )
225
224
 
226
225
  # associate new company to user
227
226
  new_user.companies.append(company)
228
227
 
228
+ # and create in the database
229
229
  self.profile_repo.create_user(new_user)
230
230
 
231
231
  # send email with verification
232
- self.send_verification_email(new_user, company_short_name)
232
+ if not cfg or cfg.get('verify_account', True):
233
+ self.send_verification_email(new_user, company_short_name)
234
+ message = self.i18n_service.t('flash_messages.signup_success')
233
235
 
234
- return {"message": self.i18n_service.t('flash_messages.signup_success')}
236
+ return {"message": message}
235
237
  except Exception as e:
236
238
  return {"error": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
237
239
 
@@ -275,7 +277,7 @@ class ProfileService:
275
277
  except Exception as e:
276
278
  return {"error": self.i18n_service.t('errors.general.unexpected_error')}
277
279
 
278
- def forgot_password(self, email: str, reset_url: str):
280
+ def forgot_password(self, company_short_name: str, email: str, reset_url: str):
279
281
  try:
280
282
  # Verificar si el usuario existe
281
283
  user = self.profile_repo.get_user_by_email(email)
@@ -287,7 +289,7 @@ class ProfileService:
287
289
  self.profile_repo.set_temp_code(email, temp_code)
288
290
 
289
291
  # send email to the user
290
- self.send_forgot_password_email(user, reset_url)
292
+ self.send_forgot_password_email(company_short_name, user, reset_url)
291
293
 
292
294
  return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
293
295
  except Exception as e:
@@ -321,19 +323,44 @@ class ProfileService:
321
323
  def get_company_by_short_name(self, short_name: str) -> Company:
322
324
  return self.profile_repo.get_company_by_short_name(short_name)
323
325
 
326
+ def get_company_users(self, company_short_name: str) -> List[Dict]:
327
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
328
+ if not company:
329
+ return []
330
+
331
+ # get the company users from the repo
332
+ company_users = self.profile_repo.get_company_users_with_details(company_short_name)
333
+
334
+ users_data = []
335
+ for user, role, last_access in company_users:
336
+ users_data.append({
337
+ "first_name": user.first_name,
338
+ "last_name": user.last_name,
339
+ "email": user.email,
340
+ "created": user.created_at,
341
+ "verified": user.verified,
342
+ "role": role or "user",
343
+ "last_access": last_access
344
+ })
345
+
346
+ return users_data
347
+
324
348
  def get_active_api_key_entry(self, api_key_value: str) -> ApiKey | None:
325
349
  return self.profile_repo.get_active_api_key_entry(api_key_value)
326
350
 
327
- def new_api_key(self, company_short_name: str):
351
+ def new_api_key(self, company_short_name: str, key_name: str):
328
352
  company = self.get_company_by_short_name(company_short_name)
329
353
  if not company:
330
354
  return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
331
355
 
356
+ if not key_name:
357
+ return {"error": self.i18n_service.t('errors.auth.api_key_name_required')}
358
+
332
359
  length = 40 # lenght of the api key
333
360
  alphabet = string.ascii_letters + string.digits
334
361
  key = ''.join(secrets.choice(alphabet) for i in range(length))
335
362
 
336
- api_key = ApiKey(key=key, company_id=company.id)
363
+ api_key = ApiKey(key=key, company_id=company.id, key_name=key_name)
337
364
  self.profile_repo.create_api_key(api_key)
338
365
  return {"api-key": key}
339
366
 
@@ -388,9 +415,12 @@ class ProfileService:
388
415
  </body>
389
416
  </html>
390
417
  """
391
- self.mail_app.send_email(to=new_user.email, subject=subject, body=body)
418
+ self.mail_service.send_mail(company_short_name=company_short_name,
419
+ recipient=new_user.email,
420
+ subject=subject,
421
+ body=body)
392
422
 
393
- def send_forgot_password_email(self, user: User, reset_url: str):
423
+ def send_forgot_password_email(self, company_short_name: str, user: User, reset_url: str):
394
424
  # send email to the user
395
425
  subject = f"Recuperación de Contraseña "
396
426
  body = f"""
@@ -441,5 +471,8 @@ class ProfileService:
441
471
  </html>
442
472
  """
443
473
 
444
- self.mail_app.send_email(to=user.email, subject=subject, body=body)
445
- return {"message": "se envio mail para cambio de clave"}
474
+ self.mail_service.send_mail(company_short_name=company_short_name,
475
+ recipient=user.email,
476
+ subject=subject,
477
+ body=body)
478
+ return {"message": self.i18n_service.t('services.mail_change_password') }
@@ -4,27 +4,144 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from injector import inject
7
+ from iatoolkit.common.interfaces.asset_storage import AssetRepository, AssetType
7
8
  from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
8
9
  from iatoolkit.services.i18n_service import I18nService
9
10
  from iatoolkit.repositories.profile_repo import ProfileRepo
10
11
  from collections import defaultdict
11
12
  from iatoolkit.repositories.models import Prompt, PromptCategory, Company
12
- import os
13
13
  from iatoolkit.common.exceptions import IAToolkitException
14
14
  import importlib.resources
15
15
  import logging
16
+ import os
16
17
 
18
+ # iatoolkit system prompts definitions
19
+ _SYSTEM_PROMPTS = [
20
+ {'name': 'query_main', 'description': 'iatoolkit main prompt'},
21
+ {'name': 'format_styles', 'description': 'output format styles'},
22
+ {'name': 'sql_rules', 'description': 'instructions for SQL queries'}
23
+ ]
17
24
 
18
25
  class PromptService:
19
26
  @inject
20
27
  def __init__(self,
28
+ asset_repo: AssetRepository,
21
29
  llm_query_repo: LLMQueryRepo,
22
30
  profile_repo: ProfileRepo,
23
31
  i18n_service: I18nService):
32
+ self.asset_repo = asset_repo
24
33
  self.llm_query_repo = llm_query_repo
25
34
  self.profile_repo = profile_repo
26
35
  self.i18n_service = i18n_service
27
36
 
37
+ def sync_company_prompts(self, company_short_name: str, prompts_config: list, categories_config: list):
38
+ """
39
+ Synchronizes prompt categories and prompts from YAML config to Database.
40
+ Strategies:
41
+ - Categories: Create or Update existing based on name.
42
+ - Prompts: Create or Update existing based on name. Soft-delete or Delete unused.
43
+ """
44
+ if not prompts_config:
45
+ return
46
+
47
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
48
+ if not company:
49
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
50
+ f'Company {company_short_name} not found')
51
+
52
+ try:
53
+ # 1. Sync Categories
54
+ category_map = {}
55
+
56
+ for i, category_name in enumerate(categories_config):
57
+ category_obj = PromptCategory(
58
+ company_id=company.id,
59
+ name=category_name,
60
+ order=i + 1
61
+ )
62
+ # Persist and get back the object with ID
63
+ persisted_cat = self.llm_query_repo.create_or_update_prompt_category(category_obj)
64
+ category_map[category_name] = persisted_cat
65
+
66
+ # 2. Sync Prompts
67
+ defined_prompt_names = set()
68
+
69
+ for prompt_data in prompts_config:
70
+ category_name = prompt_data.get('category')
71
+ if not category_name or category_name not in category_map:
72
+ logging.warning(
73
+ f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
74
+ continue
75
+
76
+ prompt_name = prompt_data['name']
77
+ defined_prompt_names.add(prompt_name)
78
+
79
+ category_obj = category_map[category_name]
80
+ filename = f"{prompt_name}.prompt"
81
+
82
+ new_prompt = Prompt(
83
+ company_id=company.id,
84
+ name=prompt_name,
85
+ description=prompt_data.get('description'),
86
+ order=prompt_data.get('order'),
87
+ category_id=category_obj.id,
88
+ active=prompt_data.get('active', True),
89
+ is_system_prompt=False,
90
+ filename=filename,
91
+ custom_fields=prompt_data.get('custom_fields', [])
92
+ )
93
+
94
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
95
+
96
+ # 3. Cleanup: Delete prompts present in DB but not in Config
97
+ existing_prompts = self.llm_query_repo.get_prompts(company)
98
+ for p in existing_prompts:
99
+ if p.name not in defined_prompt_names:
100
+ # Using hard delete to keep consistent with previous "refresh" behavior
101
+ self.llm_query_repo.session.delete(p)
102
+
103
+ self.llm_query_repo.commit()
104
+
105
+ except Exception as e:
106
+ self.llm_query_repo.rollback()
107
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
108
+
109
+ def register_system_prompts(self):
110
+ """
111
+ Synchronizes system prompts defined in Dispatcher/Code to Database.
112
+ """
113
+ try:
114
+ defined_names = set()
115
+
116
+ for i, prompt_data in enumerate(_SYSTEM_PROMPTS):
117
+ prompt_name = prompt_data['name']
118
+ defined_names.add(prompt_name)
119
+
120
+ new_prompt = Prompt(
121
+ company_id=None, # System prompts have no company
122
+ name=prompt_name,
123
+ description=prompt_data['description'],
124
+ order=i + 1,
125
+ category_id=None,
126
+ active=True,
127
+ is_system_prompt=True,
128
+ filename=f"{prompt_name}.prompt",
129
+ custom_fields=[]
130
+ )
131
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
132
+
133
+ # Cleanup old system prompts
134
+ existing_sys_prompts = self.llm_query_repo.get_system_prompts()
135
+ for p in existing_sys_prompts:
136
+ if p.name not in defined_names:
137
+ self.llm_query_repo.session.delete(p)
138
+
139
+ self.llm_query_repo.commit()
140
+
141
+ except Exception as e:
142
+ self.llm_query_repo.rollback()
143
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
144
+
28
145
  def create_prompt(self,
29
146
  prompt_name: str,
30
147
  description: str,
@@ -35,19 +152,19 @@ class PromptService:
35
152
  is_system_prompt: bool = False,
36
153
  custom_fields: list = []
37
154
  ):
38
-
155
+ """
156
+ Direct creation method (used by sync or direct calls).
157
+ Validates file existence before creating DB entry.
158
+ """
39
159
  prompt_filename = prompt_name.lower() + '.prompt'
40
160
  if is_system_prompt:
41
161
  if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
42
162
  raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
43
163
  f'missing system prompt file: {prompt_filename}')
44
164
  else:
45
- template_dir = f'companies/{company.short_name}/prompts'
46
-
47
- relative_prompt_path = os.path.join(template_dir, prompt_filename)
48
- if not os.path.exists(relative_prompt_path):
165
+ if not self.asset_repo.exists(company.short_name, AssetType.PROMPT, prompt_filename):
49
166
  raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
50
- f'missing prompt file: {relative_prompt_path}')
167
+ f'missing prompt file: {prompt_filename} in prompts/')
51
168
 
52
169
  if custom_fields:
53
170
  for f in custom_fields:
@@ -79,33 +196,28 @@ class PromptService:
79
196
 
80
197
  def get_prompt_content(self, company: Company, prompt_name: str):
81
198
  try:
82
- user_prompt_content = []
83
- execution_dir = os.getcwd()
84
-
85
199
  # get the user prompt
86
200
  user_prompt = self.llm_query_repo.get_prompt_by_name(company, prompt_name)
87
201
  if not user_prompt:
88
202
  raise IAToolkitException(IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND,
89
203
  f"prompt not found '{prompt_name}' for company '{company.short_name}'")
90
204
 
91
- prompt_file = f'companies/{company.short_name}/prompts/{user_prompt.filename}'
92
- absolute_filepath = os.path.join(execution_dir, prompt_file)
93
- if not os.path.exists(absolute_filepath):
94
- raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
95
- f"prompt file '{prompt_name}' does not exist: {absolute_filepath}")
96
-
97
205
  try:
98
- with open(absolute_filepath, 'r', encoding='utf-8') as f:
99
- user_prompt_content = f.read()
206
+ user_prompt_content = self.asset_repo.read_text(
207
+ company.short_name,
208
+ AssetType.PROMPT,
209
+ user_prompt.filename
210
+ )
211
+ except FileNotFoundError:
212
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
213
+ f"prompt file '{user_prompt.filename}' does not exist for company '{company.short_name}'")
100
214
  except Exception as e:
101
215
  raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
102
- f"error while reading prompt: '{prompt_name}' in this pathname {absolute_filepath}: {e}")
216
+ f"error while reading prompt: '{prompt_name}': {e}")
103
217
 
104
218
  return user_prompt_content
105
219
 
106
220
  except IAToolkitException:
107
- # Vuelve a lanzar las IAToolkitException que ya hemos manejado
108
- # para que no sean capturadas por el siguiente bloque.
109
221
  raise
110
222
  except Exception as e:
111
223
  logging.exception(
@@ -151,7 +263,7 @@ class PromptService:
151
263
  # get all the prompts
152
264
  all_prompts = self.llm_query_repo.get_prompts(company)
153
265
 
154
- # Agrupar prompts por categoría
266
+ # group by category
155
267
  prompts_by_category = defaultdict(list)
156
268
  for prompt in all_prompts:
157
269
  if prompt.active:
@@ -159,14 +271,13 @@ class PromptService:
159
271
  cat_key = (prompt.category.order, prompt.category.name)
160
272
  prompts_by_category[cat_key].append(prompt)
161
273
 
162
- # Ordenar los prompts dentro de cada categoría
274
+ # sort each category by order
163
275
  for cat_key in prompts_by_category:
164
276
  prompts_by_category[cat_key].sort(key=lambda p: p.order)
165
277
 
166
- # Crear la estructura de respuesta final, ordenada por la categoría
167
278
  categorized_prompts = []
168
279
 
169
- # Ordenar las categorías por su 'order'
280
+ # sort categories by order
170
281
  sorted_categories = sorted(prompts_by_category.items(), key=lambda item: item[0][0])
171
282
 
172
283
  for (cat_order, cat_name), prompts in sorted_categories: