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
@@ -3,9 +3,10 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from typing import Dict, Type, Any
6
+ from typing import Dict, Type, Any, Optional
7
7
  from .base_company import BaseCompany
8
8
  import logging
9
+ from injector import inject
9
10
 
10
11
 
11
12
  class CompanyRegistry:
@@ -14,10 +15,35 @@ class CompanyRegistry:
14
15
  Allow the client to register companies and instantiate them with dependency injection.
15
16
  """
16
17
 
18
+ @inject
17
19
  def __init__(self):
18
20
  self._company_classes: Dict[str, Type[BaseCompany]] = {}
19
21
  self._company_instances: Dict[str, BaseCompany] = {}
20
22
 
23
+ def register(self, name: str, company_class: Type[BaseCompany]) -> None:
24
+ """
25
+ Registers a company in the registry.
26
+
27
+ COMMUNITY EDITION LIMITATION:
28
+ This base implementation enforces a strict single-tenant limit.
29
+ It raises a RuntimeError if a second company is registered.
30
+ """
31
+ if not issubclass(company_class, BaseCompany):
32
+ raise ValueError(f"The class {company_class.__name__} must be a subclass of BaseCompany")
33
+
34
+ company_key = name.lower()
35
+
36
+ # --- STRICT SINGLE-TENANT ENFORCEMENT ---
37
+ # If a company is already registered (and it's not an update to the same key)
38
+ if len(self._company_classes) > 0 and company_key not in self._company_classes:
39
+ logging.error(f"❌ Community Edition Restriction: Cannot register '{name}'. Limit reached (1).")
40
+ raise RuntimeError(
41
+ "IAToolkit Community Edition allows only one company instance. "
42
+ "Upgrade to IAToolkit Enterprise to enable multi-tenancy."
43
+ )
44
+
45
+ self._company_classes[company_key] = company_class
46
+ logging.info(f"Company registered: {name}")
21
47
 
22
48
  def instantiate_companies(self, injector) -> Dict[str, BaseCompany]:
23
49
  """
@@ -34,15 +60,16 @@ class CompanyRegistry:
34
60
 
35
61
  except Exception as e:
36
62
  logging.error(f"Error while creating company instance for {company_key}: {e}")
37
- logging.exception(e)
38
- raise
63
+ raise e
39
64
 
40
65
  return self._company_instances.copy()
41
66
 
42
67
  def get_all_company_instances(self) -> Dict[str, BaseCompany]:
43
- """Devuelve un diccionario con todas las instancias de empresas creadas."""
44
68
  return self._company_instances.copy()
45
69
 
70
+ def get_company_instance(self, company_name: str) -> Optional[BaseCompany]:
71
+ return self._company_instances.get(company_name.lower())
72
+
46
73
  def get_registered_companies(self) -> Dict[str, Type[BaseCompany]]:
47
74
  return self._company_classes.copy()
48
75
 
@@ -50,11 +77,33 @@ class CompanyRegistry:
50
77
  self._company_classes.clear()
51
78
  self._company_instances.clear()
52
79
 
80
+ # --- Singleton Management ---
53
81
 
54
- # global instance of the company registry
82
+ # Global instance (Default: Community Edition)
55
83
  _company_registry = CompanyRegistry()
56
84
 
57
85
 
86
+ def get_company_registry() -> CompanyRegistry:
87
+ """Get the global company registry instance."""
88
+ return _company_registry
89
+
90
+ def get_registered_companies() -> Dict[str, Type[BaseCompany]]:
91
+ return _company_registry.get_registered_companies()
92
+
93
+
94
+ def set_company_registry(registry: CompanyRegistry) -> None:
95
+ """
96
+ Sets the global company registry instance.
97
+ Use this to inject an Enterprise-compatible registry implementation.
98
+ """
99
+ global _company_registry
100
+ if not isinstance(registry, CompanyRegistry):
101
+ raise ValueError("Registry must inherit from CompanyRegistry")
102
+
103
+ _company_registry = registry
104
+ logging.info(f"✅ Company Registry implementation swapped: {type(registry).__name__}")
105
+
106
+
58
107
  def register_company(name: str, company_class: Type[BaseCompany]) -> None:
59
108
  """
60
109
  Public function to register a company.
@@ -63,13 +112,5 @@ def register_company(name: str, company_class: Type[BaseCompany]) -> None:
63
112
  name: Name of the company
64
113
  company_class: Class that inherits from BaseCompany
65
114
  """
66
- if not issubclass(company_class, BaseCompany):
67
- raise ValueError(f"La clase {company_class.__name__} debe heredar de BaseCompany")
68
-
69
- company_key = name.lower()
70
- _company_registry._company_classes[company_key] = company_class
115
+ _company_registry.register(name, company_class)
71
116
 
72
-
73
- def get_company_registry() -> CompanyRegistry:
74
- """get the global company registry instance"""
75
- return _company_registry
@@ -3,27 +3,31 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from flask import Flask, url_for, get_flashed_messages
6
+ from flask import Flask, url_for, get_flashed_messages, request
7
7
  from flask_session import Session
8
8
  from flask_injector import FlaskInjector
9
9
  from flask_bcrypt import Bcrypt
10
10
  from flask_cors import CORS
11
11
  from iatoolkit.common.exceptions import IAToolkitException
12
+ from iatoolkit.repositories.database_manager import DatabaseManager
13
+ from iatoolkit.common.interfaces.asset_storage import AssetRepository
14
+ from iatoolkit.company_registry import get_registered_companies
15
+ from werkzeug.middleware.proxy_fix import ProxyFix
16
+ from injector import Binder, Injector, singleton
17
+ from typing import Optional, Dict, Any
12
18
  from urllib.parse import urlparse
13
19
  import redis
14
20
  import logging
15
21
  import os
16
- from typing import Optional, Dict, Any
17
- from iatoolkit.repositories.database_manager import DatabaseManager
18
- from werkzeug.middleware.proxy_fix import ProxyFix
19
- from injector import Binder, Injector, singleton
20
- from importlib.metadata import version as _pkg_version, PackageNotFoundError
21
22
 
22
- IATOOLKIT_VERSION = "0.71.4"
23
+ from iatoolkit import __version__ as IATOOLKIT_VERSION
24
+ from iatoolkit.services.configuration_service import ConfigurationService
23
25
 
24
26
  # global variable for the unique instance of IAToolkit
25
27
  _iatoolkit_instance: Optional['IAToolkit'] = None
26
28
 
29
+ def is_bound(injector: Injector, cls) -> bool:
30
+ return cls in injector.binder._bindings
27
31
 
28
32
  class IAToolkit:
29
33
  """
@@ -51,8 +55,9 @@ class IAToolkit:
51
55
  self.config = config or {}
52
56
  self.app = None
53
57
  self.db_manager = None
54
- self._injector = None
55
- self.version = IATOOLKIT_VERSION # default version
58
+ self._injector = Injector() # init empty injector
59
+ self.version = IATOOLKIT_VERSION
60
+ self.license = "Community Edition"
56
61
 
57
62
  @classmethod
58
63
  def get_instance(cls) -> 'IAToolkit':
@@ -62,7 +67,7 @@ class IAToolkit:
62
67
  _iatoolkit_instance = cls()
63
68
  return _iatoolkit_instance
64
69
 
65
- def create_iatoolkit(self):
70
+ def create_iatoolkit(self, start: bool = True):
66
71
  """
67
72
  Creates, configures, and returns the Flask application instance.
68
73
  this is the main entry point for the application factory.
@@ -78,8 +83,8 @@ class IAToolkit:
78
83
  # Step 2: Set up the core components that DI depends on
79
84
  self._setup_database()
80
85
 
81
- # Step 3: Create the Injector and configure all dependencies in one place
82
- self._injector = Injector(self._configure_core_dependencies)
86
+ # Step 3: Configure dependencies using the existing injector
87
+ self._configure_core_dependencies(self._injector)
83
88
 
84
89
  # Step 4: Register routes using the fully configured injector
85
90
  self._register_routes()
@@ -88,27 +93,42 @@ class IAToolkit:
88
93
  # and other integrations, as views are handled manually.
89
94
  FlaskInjector(app=self.app, injector=self._injector)
90
95
 
91
- # Step 6: initialize dispatcher and registered companies
92
- self._init_dispatcher_and_company_instances()
96
+ # Step 6: initialize registered companies
97
+ self._instantiate_company_instances()
93
98
 
94
99
  # Re-apply logging configuration in case it was modified by company-specific code
95
100
  self._setup_logging()
96
101
 
97
- # Step 7: Finalize setup within the application context
102
+ # Step 7: load company configuration file
103
+ self._load_company_configuration()
104
+
105
+ # Step 8: Finalize setup within the application context
98
106
  self._setup_redis_sessions()
107
+
99
108
  self._setup_cors()
100
109
  self._setup_additional_services()
101
110
  self._setup_cli_commands()
102
111
  self._setup_request_globals()
103
112
  self._setup_context_processors()
104
113
 
105
- # Step 8: define the download_dir for excel's
114
+ # Step 9: define the download_dir
106
115
  self._setup_download_dir()
107
116
 
108
- logging.info(f"🎉 IAToolkit v{self.version} inicializado correctamente")
117
+ # register data source
118
+ if start:
119
+ self.register_data_sources()
120
+
121
+ logging.info(f"🎉 IAToolkit {self.license} version {self.version} correctly initialized.")
109
122
  self._initialized = True
123
+
110
124
  return self.app
111
125
 
126
+ def register_data_sources(self):
127
+ # load the company configurations
128
+ configuration_service = self._injector.get(ConfigurationService)
129
+ for company in get_registered_companies():
130
+ configuration_service.register_data_sources(company)
131
+
112
132
  def _get_config_value(self, key: str, default=None):
113
133
  # get a value from the config dict or the environment variable
114
134
  return self.config.get(key, os.getenv(key, default))
@@ -141,14 +161,15 @@ class IAToolkit:
141
161
  force=True
142
162
  )
143
163
 
164
+ logging.getLogger("httpx").setLevel(logging.WARNING)
165
+
144
166
  def _register_routes(self):
145
167
  """Registers routes by passing the configured injector."""
146
168
  from iatoolkit.common.routes import register_views
147
169
 
148
170
  # Pass the injector to the view registration function
149
- register_views(self._injector, self.app)
150
-
151
- logging.info("✅ Routes registered.")
171
+ register_views(self.app)
172
+ logging.info("✅ Community routes registered.")
152
173
 
153
174
  def _create_flask_instance(self):
154
175
  static_folder = self._get_config_value('STATIC_FOLDER') or self._get_default_static_folder()
@@ -158,12 +179,6 @@ class IAToolkit:
158
179
  static_folder=static_folder,
159
180
  template_folder=template_folder)
160
181
 
161
- # get the IATOOLKIT_VERSION from the package metadata
162
- try:
163
- self.version = _pkg_version("iatoolkit")
164
- except PackageNotFoundError:
165
- pass
166
-
167
182
  self.app.config.update({
168
183
  'VERSION': self.version,
169
184
  'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
@@ -171,15 +186,10 @@ class IAToolkit:
171
186
  'SESSION_COOKIE_SECURE': True,
172
187
  'SESSION_PERMANENT': False,
173
188
  'SESSION_USE_SIGNER': True,
174
- 'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
189
+ 'IATOOLKIT_SECRET_KEY': self._get_config_value('IATOOLKIT_SECRET_KEY', 'iatoolkit-jwt-secret'),
175
190
  'JWT_ALGORITHM': 'HS256',
176
- 'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
177
191
  })
178
192
 
179
- parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
180
- if parsed_url.scheme == 'https':
181
- self.app.config['PREFERRED_URL_SCHEME'] = 'https'
182
-
183
193
  # 2. ProxyFix para no tener problemas con iframes y rutas
184
194
  self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
185
195
 
@@ -188,16 +198,16 @@ class IAToolkit:
188
198
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
189
199
 
190
200
  def _setup_database(self):
191
- database_uri = self._get_config_value('DATABASE_URI')
201
+ database_uri = self._get_config_value('DATABASE_URI') or self._get_config_value('DATABASE_URL')
192
202
  if not database_uri:
193
203
  raise IAToolkitException(
194
204
  IAToolkitException.ErrorType.CONFIG_ERROR,
195
- "DATABASE_URI es requerida (config dict o variable de entorno)"
205
+ "DATABASE_URI is required (config dict or env. variable)"
196
206
  )
197
207
 
198
- self.db_manager = DatabaseManager(database_uri)
208
+ self.db_manager = DatabaseManager(database_url=database_uri, schema='iatoolkit')
199
209
  self.db_manager.create_all()
200
- logging.info("✅ Base de datos configurada correctamente")
210
+ logging.info("✅ Database configured successfully")
201
211
 
202
212
  @self.app.teardown_appcontext
203
213
  def remove_session(exception=None):
@@ -211,7 +221,7 @@ class IAToolkit:
211
221
  def _setup_redis_sessions(self):
212
222
  redis_url = self._get_config_value('REDIS_URL')
213
223
  if not redis_url:
214
- logging.warning("⚠️ REDIS_URL no configurada, usando sesiones en memoria")
224
+ logging.warning("⚠️ REDIS_URL not configured, will use memory sessions")
215
225
  return
216
226
 
217
227
  try:
@@ -230,27 +240,26 @@ class IAToolkit:
230
240
  })
231
241
 
232
242
  Session(self.app)
233
- logging.info("✅ Redis y sesiones configurados correctamente")
243
+ logging.info("✅ Redis and sessions configured successfully")
234
244
 
235
245
  except Exception as e:
236
- logging.error(f"❌ Error configurando Redis: {e}")
237
- logging.warning("⚠️ Continuando sin Redis")
246
+ logging.error(f"❌ Error configuring Redis: {e}")
247
+ raise e
238
248
 
239
249
  def _setup_cors(self):
240
250
  """🌐 Configura CORS"""
241
251
  from iatoolkit.company_registry import get_company_registry
242
252
 
243
253
  # default CORS origin
244
- default_origins = [
245
- os.getenv('IATOOLKIT_BASE_URL')
246
- ]
254
+ default_origins = []
247
255
 
248
256
  # Iterate through the registered company names
249
257
  extra_origins = []
250
258
  all_company_instances = get_company_registry().get_all_company_instances()
251
259
  for company_name, company_instance in all_company_instances.items():
252
- cors_origin = company_instance.company.parameters.get('cors_origin', [])
253
- extra_origins += cors_origin
260
+ if company_instance.company:
261
+ cors_origin = company_instance.company.parameters.get('cors_origin', [])
262
+ extra_origins += cors_origin
254
263
 
255
264
  all_origins = default_origins + extra_origins
256
265
 
@@ -263,10 +272,13 @@ class IAToolkit:
263
272
  ],
264
273
  methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
265
274
 
266
- logging.info(f"✅ CORS configurado para: {all_origins}")
275
+ logging.info(f"✅ CORS configured for: {all_origins}")
267
276
 
268
- def _configure_core_dependencies(self, binder: Binder):
277
+ def _configure_core_dependencies(self, injector: Injector):
269
278
  """⚙️ Configures all system dependencies."""
279
+
280
+ # get the binder from injector
281
+ binder = injector.binder
270
282
  try:
271
283
  # Core dependencies
272
284
  binder.bind(Flask, to=self.app)
@@ -277,13 +289,13 @@ class IAToolkit:
277
289
  self._bind_services(binder)
278
290
  self._bind_infrastructure(binder)
279
291
 
280
- logging.info("✅ Dependencias configuradas correctamente")
292
+ logging.info("✅ Dependencies configured successfully")
281
293
 
282
294
  except Exception as e:
283
- logging.error(f"❌ Error configurando dependencias: {e}")
295
+ logging.error(f"❌ Error configuring dependencies: {e}")
284
296
  raise IAToolkitException(
285
297
  IAToolkitException.ErrorType.CONFIG_ERROR,
286
- f"❌ Error configurando dependencias: {e}"
298
+ f"❌ Error configuring dependencies: {e}"
287
299
  )
288
300
 
289
301
  def _bind_repositories(self, binder: Binder):
@@ -291,20 +303,22 @@ class IAToolkit:
291
303
  from iatoolkit.repositories.profile_repo import ProfileRepo
292
304
  from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
293
305
  from iatoolkit.repositories.vs_repo import VSRepo
294
- from iatoolkit.repositories.tasks_repo import TaskRepo
306
+ from iatoolkit.repositories.filesystem_asset_repository import FileSystemAssetRepository
295
307
 
296
308
  binder.bind(DocumentRepo, to=DocumentRepo)
297
309
  binder.bind(ProfileRepo, to=ProfileRepo)
298
310
  binder.bind(LLMQueryRepo, to=LLMQueryRepo)
299
311
  binder.bind(VSRepo, to=VSRepo)
300
- binder.bind(TaskRepo, to=TaskRepo)
312
+
313
+ # this class can be setup befor by iatoolkit enterprise
314
+ if not is_bound(self._injector, AssetRepository):
315
+ binder.bind(AssetRepository, to=FileSystemAssetRepository)
301
316
 
302
317
  def _bind_services(self, binder: Binder):
303
318
  from iatoolkit.services.query_service import QueryService
304
- from iatoolkit.services.tasks_service import TaskService
305
319
  from iatoolkit.services.benchmark_service import BenchmarkService
306
320
  from iatoolkit.services.document_service import DocumentService
307
- from iatoolkit.services.prompt_manager_service import PromptService
321
+ from iatoolkit.services.prompt_service import PromptService
308
322
  from iatoolkit.services.excel_service import ExcelService
309
323
  from iatoolkit.services.mail_service import MailService
310
324
  from iatoolkit.services.load_documents_service import LoadDocumentsService
@@ -316,9 +330,14 @@ class IAToolkit:
316
330
  from iatoolkit.services.language_service import LanguageService
317
331
  from iatoolkit.services.configuration_service import ConfigurationService
318
332
  from iatoolkit.services.embedding_service import EmbeddingService
333
+ from iatoolkit.services.history_manager_service import HistoryManagerService
334
+ from iatoolkit.services.tool_service import ToolService
335
+ from iatoolkit.services.llm_client_service import llmClient
336
+ from iatoolkit.services.auth_service import AuthService
337
+ from iatoolkit.services.sql_service import SqlService
338
+ from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
319
339
 
320
340
  binder.bind(QueryService, to=QueryService)
321
- binder.bind(TaskService, to=TaskService)
322
341
  binder.bind(BenchmarkService, to=BenchmarkService)
323
342
  binder.bind(DocumentService, to=DocumentService)
324
343
  binder.bind(PromptService, to=PromptService)
@@ -333,32 +352,38 @@ class IAToolkit:
333
352
  binder.bind(LanguageService, to=LanguageService)
334
353
  binder.bind(ConfigurationService, to=ConfigurationService)
335
354
  binder.bind(EmbeddingService, to=EmbeddingService)
355
+ binder.bind(HistoryManagerService, to=HistoryManagerService)
356
+ binder.bind(ToolService, to=ToolService)
357
+ binder.bind(llmClient, to=llmClient)
358
+ binder.bind(AuthService, to=AuthService)
359
+ binder.bind(SqlService, to=SqlService)
360
+ binder.bind(KnowledgeBaseService, to=KnowledgeBaseService)
336
361
 
337
362
  def _bind_infrastructure(self, binder: Binder):
338
- from iatoolkit.infra.llm_client import llmClient
339
363
  from iatoolkit.infra.llm_proxy import LLMProxy
340
364
  from iatoolkit.infra.google_chat_app import GoogleChatApp
341
- from iatoolkit.infra.mail_app import MailApp
342
- from iatoolkit.services.auth_service import AuthService
365
+ from iatoolkit.infra.brevo_mail_app import BrevoMailApp
343
366
  from iatoolkit.common.util import Utility
367
+ from iatoolkit.common.model_registry import ModelRegistry
344
368
 
345
369
  binder.bind(LLMProxy, to=LLMProxy)
346
- binder.bind(llmClient, to=llmClient)
347
370
  binder.bind(GoogleChatApp, to=GoogleChatApp)
348
- binder.bind(MailApp, to=MailApp)
349
- binder.bind(AuthService, to=AuthService)
371
+ binder.bind(BrevoMailApp, to=BrevoMailApp)
350
372
  binder.bind(Utility, to=Utility)
373
+ binder.bind(ModelRegistry, to=ModelRegistry)
351
374
 
352
375
  def _setup_additional_services(self):
353
376
  Bcrypt(self.app)
354
377
 
355
- def _init_dispatcher_and_company_instances(self):
378
+ def _instantiate_company_instances(self):
356
379
  from iatoolkit.company_registry import get_company_registry
357
- from iatoolkit.services.dispatcher_service import Dispatcher
358
380
 
359
381
  # instantiate all the registered companies
360
382
  get_company_registry().instantiate_companies(self._injector)
361
383
 
384
+ def _load_company_configuration(self):
385
+ from iatoolkit.services.dispatcher_service import Dispatcher
386
+
362
387
  # use the dispatcher to load the company config.yaml file and prepare the execution
363
388
  dispatcher = self._injector.get(Dispatcher)
364
389
  dispatcher.load_company_configs()
@@ -369,17 +394,18 @@ class IAToolkit:
369
394
 
370
395
  # 1. Register core commands
371
396
  register_core_commands(self.app)
372
- logging.info("✅ Comandos CLI del núcleo registrados.")
397
+ logging.info("✅ Core CLI commands registered.")
373
398
 
374
399
  # 2. Register company-specific commands
375
400
  try:
376
401
  # Iterate through the registered company names
377
402
  all_company_instances = get_company_registry().get_all_company_instances()
378
403
  for company_name, company_instance in all_company_instances.items():
379
- company_instance.register_cli_commands(self.app)
404
+ if hasattr(company_instance, "register_cli_commands"):
405
+ company_instance.register_cli_commands(self.app)
380
406
 
381
407
  except Exception as e:
382
- logging.error(f"❌ Error durante el registro de comandos de compañías: {e}")
408
+ logging.error(f"❌ error while registering company commands: {e}")
383
409
 
384
410
  def _setup_context_processors(self):
385
411
  # Configura context processors para templates
@@ -403,15 +429,18 @@ class IAToolkit:
403
429
 
404
430
  return {
405
431
  'url_for': url_for,
406
- 'iatoolkit_version': self.version,
432
+ 'iatoolkit_version': f'{self.version}',
433
+ 'license': self.license,
407
434
  'app_name': 'IAToolkit',
408
435
  'user_identifier': SessionManager.get('user_identifier'),
409
436
  'company_short_name': SessionManager.get('company_short_name'),
437
+ 'user_role': user_profile.get('user_role'),
410
438
  'user_is_local': user_profile.get('user_is_local'),
411
439
  'user_email': user_profile.get('user_email'),
412
- 'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
440
+ 'iatoolkit_base_url': request.url_root,
413
441
  'flashed_messages': get_flashed_messages(with_categories=True),
414
- 't': translate_for_template
442
+ 't': translate_for_template,
443
+ 'google_analytics_id': self._get_config_value('GOOGLE_ANALYTICS_ID', ''),
415
444
  }
416
445
 
417
446
  def _get_default_static_folder(self) -> str:
@@ -442,7 +471,7 @@ class IAToolkit:
442
471
  if not self._injector:
443
472
  raise IAToolkitException(
444
473
  IAToolkitException.ErrorType.CONFIG_ERROR,
445
- "App no inicializada. Llame a create_app() primero"
474
+ "App no initialized. Call create_app() first"
446
475
  )
447
476
  return self._injector.get(Dispatcher)
448
477
 
@@ -450,7 +479,7 @@ class IAToolkit:
450
479
  if not self.db_manager:
451
480
  raise IAToolkitException(
452
481
  IAToolkitException.ErrorType.CONFIG_ERROR,
453
- "Database manager no inicializado"
482
+ "Database manager not initialized."
454
483
  )
455
484
  return self.db_manager
456
485
 
@@ -459,7 +488,7 @@ class IAToolkit:
459
488
  default_download_dir = os.path.join(os.getcwd(), 'iatoolkit-downloads')
460
489
 
461
490
  # 3. if user specified one, use it
462
- download_dir = self.app.config.get('IATOOLKIT_DOWNLOAD_DIR', default_download_dir)
491
+ download_dir = self._get_config_value('IATOOLKIT_DOWNLOAD_DIR', default_download_dir)
463
492
 
464
493
  # 3. save it in the app config
465
494
  self.app.config['IATOOLKIT_DOWNLOAD_DIR'] = download_dir
@@ -475,6 +504,7 @@ class IAToolkit:
475
504
  logging.info(f"✅ download dir created in: {download_dir}")
476
505
 
477
506
 
507
+
478
508
  def current_iatoolkit() -> IAToolkit:
479
509
  return IAToolkit.get_instance()
480
510
 
@@ -13,12 +13,13 @@ import logging
13
13
  MAX_ATTACH_BYTES = int(os.getenv("BREVO_MAX_ATTACH_BYTES", str(5 * 1024 * 1024))) # 5MB seguro
14
14
 
15
15
 
16
- class MailApp:
17
- def __init__(self,):
16
+ class BrevoMailApp:
17
+ def _init_brevo(self, provider_config: dict, sender: dict = None):
18
+ # config and init the brevo client
18
19
  self.configuration = sib_api_v3_sdk.Configuration()
19
- self.configuration.api_key['api-key'] = os.getenv('BREVO_API_KEY')
20
+ self.configuration.api_key['api-key'] = provider_config.get("api_key")
20
21
  self.mail_api = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(self.configuration))
21
- self.sender = {"email": "ia@iatoolkit.com", "name": "IA Toolkit"}
22
+
22
23
 
23
24
  @staticmethod
24
25
  def _strip_data_url_prefix(b64: str) -> str:
@@ -73,13 +74,20 @@ class MailApp:
73
74
 
74
75
 
75
76
  def send_email(self,
77
+ provider_config: dict,
76
78
  to: str,
77
79
  subject: str,
78
80
  body: str,
79
- sender: dict = None,
81
+ sender: dict,
80
82
  attachments: list[dict] = None):
81
- if not sender:
82
- sender = self.sender
83
+
84
+ if not provider_config.get("api_key"):
85
+ logging.error(f'Try to send brevo_mail without api_key in provider_config')
86
+ raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
87
+ f"Invalid mail configuration for Brevo: missing api-key")
88
+
89
+ # init the Brevo client
90
+ self._init_brevo(provider_config)
83
91
 
84
92
  try:
85
93
  sdk_attachments = self._normalize_attachments(attachments)
@@ -112,34 +120,4 @@ class MailApp:
112
120
  logging.exception("MAIL ERROR: %s", str(e))
113
121
  raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
114
122
  f"No se pudo enviar correo: {str(e)}") from e
115
- ''''
116
- def send_template_email(self,
117
- subject: str,
118
- recipients: list,
119
- template_name: str,
120
- context: dict,
121
- sender=None):
122
- try:
123
- # Renderiza el template con el contexto proporcionado
124
- with self.app.app_context():
125
- html_message = render_template(template_name, **context)
126
123
 
127
- # Crea el mensaje
128
- msg = Message(
129
- subject=subject,
130
- recipients=recipients,
131
- html=html_message,
132
- sender=sender or self.app.config.get('MAIL_DEFAULT_SENDER')
133
- )
134
-
135
- # Envía el correo
136
- # self.send_brevo_email(msg)
137
- pass
138
- except jinja2.exceptions.TemplateNotFound:
139
- raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
140
- f"Error: No se encontró el template '{template_name}'.")
141
- except Exception as e:
142
- raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
143
- f'No se pudo enviar correo: {str(e)}') from e
144
-
145
- '''
File without changes