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.
- iatoolkit/__init__.py +19 -7
- iatoolkit/base_company.py +1 -71
- iatoolkit/cli_commands.py +9 -21
- iatoolkit/common/exceptions.py +2 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +53 -32
- iatoolkit/common/util.py +17 -12
- iatoolkit/company_registry.py +55 -14
- iatoolkit/{iatoolkit.py → core.py} +102 -72
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +134 -4
- iatoolkit/locales/es.yaml +293 -162
- iatoolkit/repositories/database_manager.py +92 -22
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +36 -22
- iatoolkit/repositories/models.py +86 -95
- iatoolkit/repositories/profile_repo.py +64 -13
- iatoolkit/repositories/vs_repo.py +31 -28
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +96 -39
- iatoolkit/services/configuration_service.py +329 -67
- iatoolkit/services/dispatcher_service.py +51 -227
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +9 -6
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +412 -0
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
- iatoolkit/services/load_documents_service.py +18 -47
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +69 -36
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
- iatoolkit/services/query_service.py +229 -203
- iatoolkit/services/sql_service.py +116 -34
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +121 -51
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +61 -9
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +59 -3
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +28 -3
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -3
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -2
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +8 -3
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
- iatoolkit/views/login_view.py +25 -8
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-1.4.2.dist-info/METADATA +268 -0
- iatoolkit-1.4.2.dist-info/RECORD +133 -0
- iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- iatoolkit-0.71.4.dist-info/METADATA +0 -276
- iatoolkit-0.71.4.dist-info/RECORD +0 -122
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
iatoolkit/company_registry.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
55
|
-
self.version = IATOOLKIT_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:
|
|
82
|
-
self.
|
|
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
|
|
92
|
-
self.
|
|
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:
|
|
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
|
|
114
|
+
# Step 9: define the download_dir
|
|
106
115
|
self._setup_download_dir()
|
|
107
116
|
|
|
108
|
-
|
|
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.
|
|
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
|
-
'
|
|
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
|
|
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("✅
|
|
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
|
|
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
|
|
243
|
+
logging.info("✅ Redis and sessions configured successfully")
|
|
234
244
|
|
|
235
245
|
except Exception as e:
|
|
236
|
-
logging.error(f"❌ Error
|
|
237
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
|
275
|
+
logging.info(f"✅ CORS configured for: {all_origins}")
|
|
267
276
|
|
|
268
|
-
def _configure_core_dependencies(self,
|
|
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("✅
|
|
292
|
+
logging.info("✅ Dependencies configured successfully")
|
|
281
293
|
|
|
282
294
|
except Exception as e:
|
|
283
|
-
logging.error(f"❌ Error
|
|
295
|
+
logging.error(f"❌ Error configuring dependencies: {e}")
|
|
284
296
|
raise IAToolkitException(
|
|
285
297
|
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
286
|
-
f"❌ Error
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
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("✅
|
|
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
|
|
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"❌
|
|
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':
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
17
|
-
def
|
|
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'] =
|
|
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
|
-
|
|
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
|
|
81
|
+
sender: dict,
|
|
80
82
|
attachments: list[dict] = None):
|
|
81
|
-
|
|
82
|
-
|
|
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
|