iatoolkit 0.71.2__py3-none-any.whl → 0.91.1__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 (86) hide show
  1. iatoolkit/__init__.py +15 -5
  2. iatoolkit/base_company.py +4 -58
  3. iatoolkit/cli_commands.py +6 -7
  4. iatoolkit/common/exceptions.py +1 -0
  5. iatoolkit/common/routes.py +12 -28
  6. iatoolkit/common/util.py +7 -1
  7. iatoolkit/company_registry.py +50 -14
  8. iatoolkit/{iatoolkit.py → core.py} +54 -55
  9. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  10. iatoolkit/infra/llm_client.py +9 -5
  11. iatoolkit/locales/en.yaml +10 -2
  12. iatoolkit/locales/es.yaml +171 -162
  13. iatoolkit/repositories/database_manager.py +59 -14
  14. iatoolkit/repositories/llm_query_repo.py +34 -22
  15. iatoolkit/repositories/models.py +16 -18
  16. iatoolkit/repositories/profile_repo.py +5 -10
  17. iatoolkit/repositories/vs_repo.py +9 -4
  18. iatoolkit/services/auth_service.py +1 -1
  19. iatoolkit/services/branding_service.py +1 -1
  20. iatoolkit/services/company_context_service.py +19 -11
  21. iatoolkit/services/configuration_service.py +219 -46
  22. iatoolkit/services/dispatcher_service.py +31 -225
  23. iatoolkit/services/document_service.py +10 -1
  24. iatoolkit/services/embedding_service.py +43 -41
  25. iatoolkit/services/excel_service.py +50 -2
  26. iatoolkit/services/history_manager_service.py +189 -0
  27. iatoolkit/services/jwt_service.py +1 -1
  28. iatoolkit/services/language_service.py +8 -2
  29. iatoolkit/services/license_service.py +82 -0
  30. iatoolkit/services/mail_service.py +171 -25
  31. iatoolkit/services/profile_service.py +37 -32
  32. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
  33. iatoolkit/services/query_service.py +192 -191
  34. iatoolkit/services/sql_service.py +63 -12
  35. iatoolkit/services/tool_service.py +231 -0
  36. iatoolkit/services/user_feedback_service.py +18 -6
  37. iatoolkit/services/user_session_context_service.py +18 -0
  38. iatoolkit/static/images/iatoolkit_core.png +0 -0
  39. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  40. iatoolkit/static/js/chat_feedback_button.js +1 -1
  41. iatoolkit/static/js/chat_help_content.js +4 -4
  42. iatoolkit/static/js/chat_main.js +17 -5
  43. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  44. iatoolkit/static/styles/chat_iatoolkit.css +1 -1
  45. iatoolkit/static/styles/chat_public.css +28 -0
  46. iatoolkit/static/styles/documents.css +598 -0
  47. iatoolkit/static/styles/landing_page.css +223 -7
  48. iatoolkit/system_prompts/__init__.py +0 -0
  49. iatoolkit/system_prompts/query_main.prompt +2 -1
  50. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  51. iatoolkit/templates/_company_header.html +30 -5
  52. iatoolkit/templates/_login_widget.html +3 -3
  53. iatoolkit/templates/chat.html +1 -1
  54. iatoolkit/templates/forgot_password.html +3 -2
  55. iatoolkit/templates/onboarding_shell.html +1 -1
  56. iatoolkit/templates/signup.html +3 -0
  57. iatoolkit/views/base_login_view.py +1 -1
  58. iatoolkit/views/change_password_view.py +1 -1
  59. iatoolkit/views/forgot_password_view.py +9 -4
  60. iatoolkit/views/history_api_view.py +3 -3
  61. iatoolkit/views/home_view.py +4 -2
  62. iatoolkit/views/init_context_api_view.py +1 -1
  63. iatoolkit/views/llmquery_api_view.py +4 -3
  64. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
  65. iatoolkit/views/login_view.py +17 -5
  66. iatoolkit/views/logout_api_view.py +10 -2
  67. iatoolkit/views/prompt_api_view.py +1 -1
  68. iatoolkit/views/root_redirect_view.py +22 -0
  69. iatoolkit/views/signup_view.py +12 -4
  70. iatoolkit/views/static_page_view.py +27 -0
  71. iatoolkit/views/verify_user_view.py +1 -1
  72. iatoolkit-0.91.1.dist-info/METADATA +268 -0
  73. iatoolkit-0.91.1.dist-info/RECORD +125 -0
  74. iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  75. iatoolkit/services/history_service.py +0 -37
  76. iatoolkit/templates/about.html +0 -13
  77. iatoolkit/templates/index.html +0 -145
  78. iatoolkit/templates/login_simulation.html +0 -45
  79. iatoolkit/views/external_login_view.py +0 -73
  80. iatoolkit/views/index_view.py +0 -14
  81. iatoolkit/views/login_simulation_view.py +0 -93
  82. iatoolkit-0.71.2.dist-info/METADATA +0 -276
  83. iatoolkit-0.71.2.dist-info/RECORD +0 -122
  84. {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
  85. {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
  86. {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/top_level.txt +0 -0
@@ -9,9 +9,11 @@ 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.language_service import LanguageService
12
13
  from iatoolkit.services.user_session_context_service import UserSessionContextService
14
+ from iatoolkit.services.configuration_service import ConfigurationService
13
15
  from flask_bcrypt import Bcrypt
14
- from iatoolkit.infra.mail_app import MailApp
16
+ from iatoolkit.services.mail_service import MailService
15
17
  import random
16
18
  import re
17
19
  import secrets
@@ -26,13 +28,17 @@ class ProfileService:
26
28
  i18n_service: I18nService,
27
29
  profile_repo: ProfileRepo,
28
30
  session_context_service: UserSessionContextService,
31
+ config_service: ConfigurationService,
32
+ lang_service: LanguageService,
29
33
  dispatcher: Dispatcher,
30
- mail_app: MailApp):
34
+ mail_service: MailService):
31
35
  self.i18n_service = i18n_service
32
36
  self.profile_repo = profile_repo
33
37
  self.dispatcher = dispatcher
34
38
  self.session_context = session_context_service
35
- self.mail_app = mail_app
39
+ self.config_service = config_service
40
+ self.lang_service = lang_service
41
+ self.mail_service = mail_service
36
42
  self.bcrypt = Bcrypt()
37
43
 
38
44
 
@@ -61,11 +67,12 @@ class ProfileService:
61
67
 
62
68
  # 1. Build the local user profile dictionary here.
63
69
  # 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
70
+ user_identifier = user.email
65
71
  user_profile = {
66
72
  "user_email": user.email,
67
73
  "user_fullname": f'{user.first_name} {user.last_name}',
68
74
  "user_is_local": True,
75
+ "user_id": user.id,
69
76
  "extras": {}
70
77
  }
71
78
 
@@ -79,25 +86,6 @@ class ProfileService:
79
86
  logging.error(f"Error in login: {e}")
80
87
  return {'success': False, "message": str(e)}
81
88
 
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
89
  def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
102
90
  """
103
91
  Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
@@ -107,6 +95,7 @@ class ProfileService:
107
95
  user_profile['id'] = user_identifier
108
96
  user_profile['company_id'] = company.id
109
97
  user_profile['company'] = company.name
98
+ user_profile['language'] = self.lang_service.get_current_language()
110
99
 
111
100
  # save user_profile in Redis session
112
101
  self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
@@ -214,24 +203,34 @@ class ProfileService:
214
203
  # encrypt the password
215
204
  hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
216
205
 
206
+ # account verification can be skiped with this security parameter
207
+ verified = False
208
+ cfg = self.config_service.get_configuration(company_short_name, 'parameters')
209
+ if cfg and not cfg.get('verify_account', True):
210
+ verified = True
211
+ message = self.i18n_service.t('flash_messages.signup_success_no_verification')
212
+
217
213
  # create the new user
218
214
  new_user = User(email=email,
219
215
  password=hashed_password,
220
216
  first_name=first_name.lower(),
221
217
  last_name=last_name.lower(),
222
- verified=False,
218
+ verified=verified,
223
219
  verification_url=verification_url
224
220
  )
225
221
 
226
222
  # associate new company to user
227
223
  new_user.companies.append(company)
228
224
 
225
+ # and create in the database
229
226
  self.profile_repo.create_user(new_user)
230
227
 
231
228
  # send email with verification
232
- self.send_verification_email(new_user, company_short_name)
229
+ if not cfg or cfg.get('verify_account', True):
230
+ self.send_verification_email(new_user, company_short_name)
231
+ message = self.i18n_service.t('flash_messages.signup_success')
233
232
 
234
- return {"message": self.i18n_service.t('flash_messages.signup_success')}
233
+ return {"message": message}
235
234
  except Exception as e:
236
235
  return {"error": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
237
236
 
@@ -275,7 +274,7 @@ class ProfileService:
275
274
  except Exception as e:
276
275
  return {"error": self.i18n_service.t('errors.general.unexpected_error')}
277
276
 
278
- def forgot_password(self, email: str, reset_url: str):
277
+ def forgot_password(self, company_short_name: str, email: str, reset_url: str):
279
278
  try:
280
279
  # Verificar si el usuario existe
281
280
  user = self.profile_repo.get_user_by_email(email)
@@ -287,7 +286,7 @@ class ProfileService:
287
286
  self.profile_repo.set_temp_code(email, temp_code)
288
287
 
289
288
  # send email to the user
290
- self.send_forgot_password_email(user, reset_url)
289
+ self.send_forgot_password_email(company_short_name, user, reset_url)
291
290
 
292
291
  return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
293
292
  except Exception as e:
@@ -388,9 +387,12 @@ class ProfileService:
388
387
  </body>
389
388
  </html>
390
389
  """
391
- self.mail_app.send_email(to=new_user.email, subject=subject, body=body)
390
+ self.mail_service.send_mail(company_short_name=company_short_name,
391
+ recipient=new_user.email,
392
+ subject=subject,
393
+ body=body)
392
394
 
393
- def send_forgot_password_email(self, user: User, reset_url: str):
395
+ def send_forgot_password_email(self, company_short_name: str, user: User, reset_url: str):
394
396
  # send email to the user
395
397
  subject = f"Recuperación de Contraseña "
396
398
  body = f"""
@@ -441,5 +443,8 @@ class ProfileService:
441
443
  </html>
442
444
  """
443
445
 
444
- self.mail_app.send_email(to=user.email, subject=subject, body=body)
445
- return {"message": "se envio mail para cambio de clave"}
446
+ self.mail_service.send_mail(company_short_name=company_short_name,
447
+ recipient=user.email,
448
+ subject=subject,
449
+ body=body)
450
+ return {"message": self.i18n_service.t('services.mail_change_password') }
@@ -14,6 +14,12 @@ from iatoolkit.common.exceptions import IAToolkitException
14
14
  import importlib.resources
15
15
  import logging
16
16
 
17
+ # iatoolkit system prompts definitions
18
+ _SYSTEM_PROMPTS = [
19
+ {'name': 'query_main', 'description': 'iatoolkit main prompt'},
20
+ {'name': 'format_styles', 'description': 'output format styles'},
21
+ {'name': 'sql_rules', 'description': 'instructions for SQL queries'}
22
+ ]
17
23
 
18
24
  class PromptService:
19
25
  @inject
@@ -25,6 +31,106 @@ class PromptService:
25
31
  self.profile_repo = profile_repo
26
32
  self.i18n_service = i18n_service
27
33
 
34
+ def sync_company_prompts(self, company_instance, prompts_config: list, categories_config: list):
35
+ """
36
+ Synchronizes prompt categories and prompts from YAML config to Database.
37
+ Strategies:
38
+ - Categories: Create or Update existing based on name.
39
+ - Prompts: Create or Update existing based on name. Soft-delete or Delete unused.
40
+ """
41
+ try:
42
+ # 1. Sync Categories
43
+ category_map = {}
44
+
45
+ for i, category_name in enumerate(categories_config):
46
+ category_obj = PromptCategory(
47
+ company_id=company_instance.company.id,
48
+ name=category_name,
49
+ order=i + 1
50
+ )
51
+ # Persist and get back the object with ID
52
+ persisted_cat = self.llm_query_repo.create_or_update_prompt_category(category_obj)
53
+ category_map[category_name] = persisted_cat
54
+
55
+ # 2. Sync Prompts
56
+ defined_prompt_names = set()
57
+
58
+ for prompt_data in prompts_config:
59
+ category_name = prompt_data.get('category')
60
+ if not category_name or category_name not in category_map:
61
+ logging.warning(
62
+ f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
63
+ continue
64
+
65
+ prompt_name = prompt_data['name']
66
+ defined_prompt_names.add(prompt_name)
67
+
68
+ category_obj = category_map[category_name]
69
+ filename = f"{prompt_name}.prompt"
70
+
71
+ new_prompt = Prompt(
72
+ company_id=company_instance.company.id,
73
+ name=prompt_name,
74
+ description=prompt_data['description'],
75
+ order=prompt_data['order'],
76
+ category_id=category_obj.id,
77
+ active=prompt_data.get('active', True),
78
+ is_system_prompt=False,
79
+ filename=filename,
80
+ custom_fields=prompt_data.get('custom_fields', [])
81
+ )
82
+
83
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
84
+
85
+ # 3. Cleanup: Delete prompts present in DB but not in Config
86
+ existing_prompts = self.llm_query_repo.get_prompts(company_instance.company)
87
+ for p in existing_prompts:
88
+ if p.name not in defined_prompt_names:
89
+ # Using hard delete to keep consistent with previous "refresh" behavior
90
+ self.llm_query_repo.session.delete(p)
91
+
92
+ self.llm_query_repo.commit()
93
+
94
+ except Exception as e:
95
+ self.llm_query_repo.rollback()
96
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
97
+
98
+ def register_system_prompts(self):
99
+ """
100
+ Synchronizes system prompts defined in Dispatcher/Code to Database.
101
+ """
102
+ try:
103
+ defined_names = set()
104
+
105
+ for i, prompt_data in enumerate(_SYSTEM_PROMPTS):
106
+ prompt_name = prompt_data['name']
107
+ defined_names.add(prompt_name)
108
+
109
+ new_prompt = Prompt(
110
+ company_id=None, # System prompts have no company
111
+ name=prompt_name,
112
+ description=prompt_data['description'],
113
+ order=i + 1,
114
+ category_id=None,
115
+ active=True,
116
+ is_system_prompt=True,
117
+ filename=f"{prompt_name}.prompt",
118
+ custom_fields=[]
119
+ )
120
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
121
+
122
+ # Cleanup old system prompts
123
+ existing_sys_prompts = self.llm_query_repo.get_system_prompts()
124
+ for p in existing_sys_prompts:
125
+ if p.name not in defined_names:
126
+ self.llm_query_repo.session.delete(p)
127
+
128
+ self.llm_query_repo.commit()
129
+
130
+ except Exception as e:
131
+ self.llm_query_repo.rollback()
132
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
133
+
28
134
  def create_prompt(self,
29
135
  prompt_name: str,
30
136
  description: str,
@@ -35,7 +141,10 @@ class PromptService:
35
141
  is_system_prompt: bool = False,
36
142
  custom_fields: list = []
37
143
  ):
38
-
144
+ """
145
+ Direct creation method (used by sync or direct calls).
146
+ Validates file existence before creating DB entry.
147
+ """
39
148
  prompt_filename = prompt_name.lower() + '.prompt'
40
149
  if is_system_prompt:
41
150
  if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():