iatoolkit 0.4.2__py3-none-any.whl → 0.66.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 +13 -35
- iatoolkit/base_company.py +74 -8
- iatoolkit/cli_commands.py +15 -23
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +46 -0
- iatoolkit/common/routes.py +141 -0
- iatoolkit/common/session_manager.py +24 -0
- iatoolkit/common/util.py +348 -0
- iatoolkit/company_registry.py +7 -8
- iatoolkit/iatoolkit.py +169 -96
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/call_service.py +140 -0
- iatoolkit/infra/connectors/__init__.py +5 -0
- iatoolkit/infra/connectors/file_connector.py +17 -0
- iatoolkit/infra/connectors/file_connector_factory.py +57 -0
- iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
- iatoolkit/infra/connectors/google_drive_connector.py +68 -0
- iatoolkit/infra/connectors/local_file_connector.py +46 -0
- iatoolkit/infra/connectors/s3_connector.py +33 -0
- iatoolkit/infra/gemini_adapter.py +356 -0
- iatoolkit/infra/google_chat_app.py +57 -0
- iatoolkit/infra/llm_client.py +429 -0
- iatoolkit/infra/llm_proxy.py +139 -0
- iatoolkit/infra/llm_response.py +40 -0
- iatoolkit/infra/mail_app.py +145 -0
- iatoolkit/infra/openai_adapter.py +90 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +144 -0
- iatoolkit/locales/es.yaml +140 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +110 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/llm_query_repo.py +91 -0
- iatoolkit/repositories/models.py +336 -0
- iatoolkit/repositories/profile_repo.py +123 -0
- iatoolkit/repositories/tasks_repo.py +52 -0
- iatoolkit/repositories/vs_repo.py +139 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +6 -6
- iatoolkit/services/branding_service.py +149 -0
- {services → iatoolkit/services}/dispatcher_service.py +39 -99
- {services → iatoolkit/services}/document_service.py +5 -5
- {services → iatoolkit/services}/excel_service.py +27 -21
- {services → iatoolkit/services}/file_processor_service.py +5 -5
- iatoolkit/services/help_content_service.py +30 -0
- {services → iatoolkit/services}/history_service.py +8 -16
- iatoolkit/services/i18n_service.py +104 -0
- {services → iatoolkit/services}/jwt_service.py +18 -27
- iatoolkit/services/language_service.py +77 -0
- {services → iatoolkit/services}/load_documents_service.py +19 -14
- {services → iatoolkit/services}/mail_service.py +5 -5
- iatoolkit/services/onboarding_service.py +43 -0
- {services → iatoolkit/services}/profile_service.py +155 -89
- {services → iatoolkit/services}/prompt_manager_service.py +26 -11
- {services → iatoolkit/services}/query_service.py +142 -104
- {services → iatoolkit/services}/search_service.py +21 -5
- {services → iatoolkit/services}/sql_service.py +24 -6
- {services → iatoolkit/services}/tasks_service.py +10 -10
- iatoolkit/services/user_feedback_service.py +103 -0
- iatoolkit/services/user_session_context_service.py +143 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_filepond.js +85 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +112 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +364 -0
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +592 -0
- iatoolkit/static/styles/chat_modal.css +169 -0
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/llm_output.css +115 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +5 -15
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/about.html +13 -0
- iatoolkit/templates/base.html +65 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +287 -0
- iatoolkit/templates/chat_modals.html +181 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +50 -0
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +104 -0
- iatoolkit/templates/signup.html +76 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +92 -0
- iatoolkit/views/change_password_view.py +117 -0
- iatoolkit/views/external_login_view.py +73 -0
- iatoolkit/views/file_store_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +72 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +61 -0
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +73 -0
- iatoolkit/views/llmquery_api_view.py +57 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +153 -0
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/signup_view.py +94 -0
- iatoolkit/views/tasks_api_view.py +72 -0
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +62 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
- iatoolkit-0.66.2.dist-info/RECORD +119 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.4.2.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
iatoolkit/iatoolkit.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
4
5
|
|
|
5
|
-
from flask import Flask, url_for,
|
|
6
|
+
from flask import Flask, url_for, get_flashed_messages
|
|
6
7
|
from flask_session import Session
|
|
7
8
|
from flask_injector import FlaskInjector
|
|
8
9
|
from flask_bcrypt import Bcrypt
|
|
9
10
|
from flask_cors import CORS
|
|
10
|
-
from common.
|
|
11
|
-
from common.util import Utility
|
|
12
|
-
from common.exceptions import IAToolkitException
|
|
13
|
-
from common.session_manager import SessionManager
|
|
11
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
14
12
|
from urllib.parse import urlparse
|
|
15
13
|
import redis
|
|
16
14
|
import logging
|
|
17
15
|
import os
|
|
18
16
|
from typing import Optional, Dict, Any
|
|
19
|
-
from repositories.database_manager import DatabaseManager
|
|
20
|
-
from
|
|
17
|
+
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
18
|
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
19
|
+
from injector import Binder, Injector, singleton
|
|
21
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
22
21
|
|
|
22
|
+
IATOOLKIT_VERSION = "0.66.2"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -52,13 +52,11 @@ class IAToolkit:
|
|
|
52
52
|
self.app = None
|
|
53
53
|
self.db_manager = None
|
|
54
54
|
self._injector = None
|
|
55
|
-
self.version =
|
|
55
|
+
self.version = IATOOLKIT_VERSION # default version
|
|
56
56
|
|
|
57
57
|
@classmethod
|
|
58
58
|
def get_instance(cls) -> 'IAToolkit':
|
|
59
|
-
|
|
60
|
-
Obtiene la instancia única de IAToolkit
|
|
61
|
-
"""
|
|
59
|
+
# get the global IAToolkit instance
|
|
62
60
|
global _iatoolkit_instance
|
|
63
61
|
if _iatoolkit_instance is None:
|
|
64
62
|
_iatoolkit_instance = cls()
|
|
@@ -90,29 +88,48 @@ class IAToolkit:
|
|
|
90
88
|
# and other integrations, as views are handled manually.
|
|
91
89
|
FlaskInjector(app=self.app, injector=self._injector)
|
|
92
90
|
|
|
93
|
-
# Step 6:
|
|
91
|
+
# Step 6: initialize dispatcher and registered companies
|
|
92
|
+
self._init_dispatcher_and_company_instances()
|
|
93
|
+
|
|
94
|
+
# Step 7: Finalize setup within the application context
|
|
94
95
|
self._setup_redis_sessions()
|
|
95
96
|
self._setup_cors()
|
|
96
97
|
self._setup_additional_services()
|
|
97
98
|
self._setup_cli_commands()
|
|
99
|
+
self._setup_request_globals()
|
|
98
100
|
self._setup_context_processors()
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
except PackageNotFoundError:
|
|
103
|
-
pass
|
|
102
|
+
# Step 8: define the download_dir for excel's
|
|
103
|
+
self._setup_download_dir()
|
|
104
104
|
|
|
105
105
|
logging.info(f"🎉 IAToolkit v{self.version} inicializado correctamente")
|
|
106
106
|
self._initialized = True
|
|
107
107
|
return self.app
|
|
108
108
|
|
|
109
109
|
def _get_config_value(self, key: str, default=None):
|
|
110
|
-
|
|
110
|
+
# get a value from the config dict or the environment variable
|
|
111
111
|
return self.config.get(key, os.getenv(key, default))
|
|
112
112
|
|
|
113
|
+
def _setup_request_globals(self):
|
|
114
|
+
"""
|
|
115
|
+
Configures functions to run before each request to set up
|
|
116
|
+
request-global variables, such as language.
|
|
117
|
+
"""
|
|
118
|
+
injector = self._injector
|
|
119
|
+
|
|
120
|
+
@self.app.before_request
|
|
121
|
+
def set_request_language():
|
|
122
|
+
"""
|
|
123
|
+
Determines and caches the language for the current request in g.lang.
|
|
124
|
+
"""
|
|
125
|
+
from iatoolkit.services.language_service import LanguageService
|
|
126
|
+
language_service = injector.get(LanguageService)
|
|
127
|
+
language_service.get_current_language()
|
|
128
|
+
|
|
113
129
|
def _setup_logging(self):
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
# Lee el nivel de log desde una variable de entorno, con 'INFO' como valor por defecto.
|
|
131
|
+
log_level_name = os.getenv('LOG_LEVEL', 'INFO').upper()
|
|
132
|
+
log_level = getattr(logging, log_level_name, logging.INFO)
|
|
116
133
|
|
|
117
134
|
logging.basicConfig(
|
|
118
135
|
level=log_level,
|
|
@@ -123,7 +140,7 @@ class IAToolkit:
|
|
|
123
140
|
|
|
124
141
|
def _register_routes(self):
|
|
125
142
|
"""Registers routes by passing the configured injector."""
|
|
126
|
-
from common.routes import register_views
|
|
143
|
+
from iatoolkit.common.routes import register_views
|
|
127
144
|
|
|
128
145
|
# Pass the injector to the view registration function
|
|
129
146
|
register_views(self._injector, self.app)
|
|
@@ -141,11 +158,21 @@ class IAToolkit:
|
|
|
141
158
|
is_https = self._get_config_value('USE_HTTPS', 'false').lower() == 'true'
|
|
142
159
|
is_dev = self._get_config_value('FLASK_ENV') == 'development'
|
|
143
160
|
|
|
161
|
+
# get the iatoolkit domain
|
|
162
|
+
parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
|
|
163
|
+
domain = parsed_url.netloc
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
self.version = _pkg_version("iatoolkit")
|
|
167
|
+
except PackageNotFoundError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
144
171
|
self.app.config.update({
|
|
145
172
|
'VERSION': self.version,
|
|
146
173
|
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
147
|
-
'SESSION_COOKIE_SAMESITE': "None"
|
|
148
|
-
'SESSION_COOKIE_SECURE':
|
|
174
|
+
'SESSION_COOKIE_SAMESITE': "None",
|
|
175
|
+
'SESSION_COOKIE_SECURE': True,
|
|
149
176
|
'SESSION_PERMANENT': False,
|
|
150
177
|
'SESSION_USE_SIGNER': True,
|
|
151
178
|
'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
|
|
@@ -153,6 +180,12 @@ class IAToolkit:
|
|
|
153
180
|
'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
|
|
154
181
|
})
|
|
155
182
|
|
|
183
|
+
if parsed_url.scheme == 'https':
|
|
184
|
+
self.app.config['PREFERRED_URL_SCHEME'] = 'https'
|
|
185
|
+
|
|
186
|
+
# 2. ProxyFix para no tener problemas con iframes y rutas
|
|
187
|
+
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
|
|
188
|
+
|
|
156
189
|
# Configuración para tokenizers en desarrollo
|
|
157
190
|
if is_dev:
|
|
158
191
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
@@ -169,6 +202,15 @@ class IAToolkit:
|
|
|
169
202
|
self.db_manager.create_all()
|
|
170
203
|
logging.info("✅ Base de datos configurada correctamente")
|
|
171
204
|
|
|
205
|
+
@self.app.teardown_appcontext
|
|
206
|
+
def remove_session(exception=None):
|
|
207
|
+
"""
|
|
208
|
+
Flask calls this after each request.
|
|
209
|
+
It ensures the SQLAlchemy session is properly closed
|
|
210
|
+
and the DB connection is returned to the pool.
|
|
211
|
+
"""
|
|
212
|
+
self.db_manager.scoped_session.remove()
|
|
213
|
+
|
|
172
214
|
def _setup_redis_sessions(self):
|
|
173
215
|
redis_url = self._get_config_value('REDIS_URL')
|
|
174
216
|
if not redis_url:
|
|
@@ -199,19 +241,19 @@ class IAToolkit:
|
|
|
199
241
|
|
|
200
242
|
def _setup_cors(self):
|
|
201
243
|
"""🌐 Configura CORS"""
|
|
202
|
-
|
|
244
|
+
from iatoolkit.company_registry import get_company_registry
|
|
245
|
+
|
|
246
|
+
# default CORS origin
|
|
203
247
|
default_origins = [
|
|
204
|
-
"http://localhost:5001",
|
|
205
|
-
"http://127.0.0.1:5001",
|
|
206
248
|
os.getenv('IATOOLKIT_BASE_URL')
|
|
207
249
|
]
|
|
208
250
|
|
|
209
|
-
#
|
|
251
|
+
# Iterate through the registered company names
|
|
210
252
|
extra_origins = []
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
253
|
+
all_company_instances = get_company_registry().get_all_company_instances()
|
|
254
|
+
for company_name, company_instance in all_company_instances.items():
|
|
255
|
+
cors_origin = company_instance.company.parameters.get('cors_origin', [])
|
|
256
|
+
extra_origins += cors_origin
|
|
215
257
|
|
|
216
258
|
all_origins = default_origins + extra_origins
|
|
217
259
|
|
|
@@ -226,19 +268,17 @@ class IAToolkit:
|
|
|
226
268
|
|
|
227
269
|
logging.info(f"✅ CORS configurado para: {all_origins}")
|
|
228
270
|
|
|
229
|
-
|
|
230
271
|
def _configure_core_dependencies(self, binder: Binder):
|
|
231
272
|
"""⚙️ Configures all system dependencies."""
|
|
232
273
|
try:
|
|
233
274
|
# Core dependencies
|
|
234
|
-
binder.bind(Flask, to=self.app
|
|
275
|
+
binder.bind(Flask, to=self.app)
|
|
235
276
|
binder.bind(DatabaseManager, to=self.db_manager, scope=singleton)
|
|
236
277
|
|
|
237
278
|
# Bind all application components by calling the specific methods
|
|
238
279
|
self._bind_repositories(binder)
|
|
239
280
|
self._bind_services(binder)
|
|
240
281
|
self._bind_infrastructure(binder)
|
|
241
|
-
self._bind_views(binder)
|
|
242
282
|
|
|
243
283
|
logging.info("✅ Dependencias configuradas correctamente")
|
|
244
284
|
|
|
@@ -250,11 +290,12 @@ class IAToolkit:
|
|
|
250
290
|
)
|
|
251
291
|
|
|
252
292
|
def _bind_repositories(self, binder: Binder):
|
|
253
|
-
from repositories.document_repo import DocumentRepo
|
|
254
|
-
from repositories.profile_repo import ProfileRepo
|
|
255
|
-
from repositories.llm_query_repo import LLMQueryRepo
|
|
256
|
-
|
|
257
|
-
from repositories.
|
|
293
|
+
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
294
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
295
|
+
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
296
|
+
|
|
297
|
+
from iatoolkit.repositories.vs_repo import VSRepo
|
|
298
|
+
from iatoolkit.repositories.tasks_repo import TaskRepo
|
|
258
299
|
|
|
259
300
|
binder.bind(DocumentRepo, to=DocumentRepo)
|
|
260
301
|
binder.bind(ProfileRepo, to=ProfileRepo)
|
|
@@ -263,17 +304,20 @@ class IAToolkit:
|
|
|
263
304
|
binder.bind(TaskRepo, to=TaskRepo)
|
|
264
305
|
|
|
265
306
|
def _bind_services(self, binder: Binder):
|
|
266
|
-
from services.query_service import QueryService
|
|
267
|
-
from services.tasks_service import TaskService
|
|
268
|
-
from services.benchmark_service import BenchmarkService
|
|
269
|
-
from services.document_service import DocumentService
|
|
270
|
-
from services.prompt_manager_service import PromptService
|
|
271
|
-
from services.excel_service import ExcelService
|
|
272
|
-
from services.mail_service import MailService
|
|
273
|
-
from services.load_documents_service import LoadDocumentsService
|
|
274
|
-
from services.profile_service import ProfileService
|
|
275
|
-
from services.jwt_service import JWTService
|
|
276
|
-
from services.dispatcher_service import Dispatcher
|
|
307
|
+
from iatoolkit.services.query_service import QueryService
|
|
308
|
+
from iatoolkit.services.tasks_service import TaskService
|
|
309
|
+
from iatoolkit.services.benchmark_service import BenchmarkService
|
|
310
|
+
from iatoolkit.services.document_service import DocumentService
|
|
311
|
+
from iatoolkit.services.prompt_manager_service import PromptService
|
|
312
|
+
from iatoolkit.services.excel_service import ExcelService
|
|
313
|
+
from iatoolkit.services.mail_service import MailService
|
|
314
|
+
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
315
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
316
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
317
|
+
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
318
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
319
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
320
|
+
from iatoolkit.services.language_service import LanguageService
|
|
277
321
|
|
|
278
322
|
binder.bind(QueryService, to=QueryService)
|
|
279
323
|
binder.bind(TaskService, to=TaskService)
|
|
@@ -285,41 +329,42 @@ class IAToolkit:
|
|
|
285
329
|
binder.bind(LoadDocumentsService, to=LoadDocumentsService)
|
|
286
330
|
binder.bind(ProfileService, to=ProfileService)
|
|
287
331
|
binder.bind(JWTService, to=JWTService)
|
|
288
|
-
binder.bind(Dispatcher, to=Dispatcher
|
|
332
|
+
binder.bind(Dispatcher, to=Dispatcher)
|
|
333
|
+
binder.bind(BrandingService, to=BrandingService)
|
|
334
|
+
binder.bind(I18nService, to=I18nService)
|
|
335
|
+
binder.bind(LanguageService, to=LanguageService)
|
|
289
336
|
|
|
290
337
|
def _bind_infrastructure(self, binder: Binder):
|
|
291
|
-
from infra.llm_client import llmClient
|
|
292
|
-
from infra.llm_proxy import LLMProxy
|
|
293
|
-
from infra.google_chat_app import GoogleChatApp
|
|
294
|
-
from infra.mail_app import MailApp
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
338
|
+
from iatoolkit.infra.llm_client import llmClient
|
|
339
|
+
from iatoolkit.infra.llm_proxy import LLMProxy
|
|
340
|
+
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
341
|
+
from iatoolkit.infra.mail_app import MailApp
|
|
342
|
+
from iatoolkit.services.auth_service import AuthService
|
|
343
|
+
from iatoolkit.common.util import Utility
|
|
344
|
+
|
|
345
|
+
binder.bind(LLMProxy, to=LLMProxy)
|
|
346
|
+
binder.bind(llmClient, to=llmClient)
|
|
298
347
|
binder.bind(GoogleChatApp, to=GoogleChatApp)
|
|
299
348
|
binder.bind(MailApp, to=MailApp)
|
|
300
|
-
binder.bind(
|
|
349
|
+
binder.bind(AuthService, to=AuthService)
|
|
301
350
|
binder.bind(Utility, to=Utility)
|
|
302
351
|
|
|
303
|
-
def
|
|
304
|
-
|
|
305
|
-
from views.llmquery_view import LLMQueryView
|
|
306
|
-
from views.home_view import HomeView
|
|
307
|
-
from views.chat_view import ChatView
|
|
308
|
-
from views.change_password_view import ChangePasswordView
|
|
352
|
+
def _setup_additional_services(self):
|
|
353
|
+
Bcrypt(self.app)
|
|
309
354
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
binder.bind(LLMQueryView, to=LLMQueryView)
|
|
355
|
+
def _init_dispatcher_and_company_instances(self):
|
|
356
|
+
from iatoolkit.company_registry import get_company_registry
|
|
357
|
+
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
314
358
|
|
|
315
|
-
|
|
359
|
+
# instantiate all the registered companies
|
|
360
|
+
get_company_registry().instantiate_companies(self._injector)
|
|
316
361
|
|
|
317
|
-
|
|
318
|
-
|
|
362
|
+
# use the dispatcher to start the execution of every company
|
|
363
|
+
dispatcher = self._injector.get(Dispatcher)
|
|
364
|
+
dispatcher.start_execution()
|
|
319
365
|
|
|
320
366
|
def _setup_cli_commands(self):
|
|
321
367
|
from iatoolkit.cli_commands import register_core_commands
|
|
322
|
-
from services.dispatcher_service import Dispatcher
|
|
323
368
|
from iatoolkit.company_registry import get_company_registry
|
|
324
369
|
|
|
325
370
|
# 1. Register core commands
|
|
@@ -328,16 +373,10 @@ class IAToolkit:
|
|
|
328
373
|
|
|
329
374
|
# 2. Register company-specific commands
|
|
330
375
|
try:
|
|
331
|
-
# Get the dispatcher, which holds the company instances
|
|
332
|
-
dispatcher = self.get_injector().get(Dispatcher)
|
|
333
|
-
registry = get_company_registry()
|
|
334
|
-
|
|
335
376
|
# Iterate through the registered company names
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
company_instance.register_cli_commands(self.app)
|
|
340
|
-
logging.info(f"✅ Comandos CLI para la compañía '{company_name}' registrados.")
|
|
377
|
+
all_company_instances = get_company_registry().get_all_company_instances()
|
|
378
|
+
for company_name, company_instance in all_company_instances.items():
|
|
379
|
+
company_instance.register_cli_commands(self.app)
|
|
341
380
|
|
|
342
381
|
except Exception as e:
|
|
343
382
|
logging.error(f"❌ Error durante el registro de comandos de compañías: {e}")
|
|
@@ -346,27 +385,46 @@ class IAToolkit:
|
|
|
346
385
|
# Configura context processors para templates
|
|
347
386
|
@self.app.context_processor
|
|
348
387
|
def inject_globals():
|
|
388
|
+
from iatoolkit.common.session_manager import SessionManager
|
|
389
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
390
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
391
|
+
|
|
392
|
+
# Get services from the injector
|
|
393
|
+
profile_service = self._injector.get(ProfileService)
|
|
394
|
+
i18n_service = self._injector.get(I18nService)
|
|
395
|
+
|
|
396
|
+
# The 't' function wrapper no longer needs to determine the language itself.
|
|
397
|
+
# It will be automatically handled by the refactored I18nService.
|
|
398
|
+
def translate_for_template(key: str, **kwargs):
|
|
399
|
+
return i18n_service.t(key, **kwargs)
|
|
400
|
+
|
|
401
|
+
# Get user profile if a session exists
|
|
402
|
+
user_profile = profile_service.get_current_session_info().get('profile', {})
|
|
403
|
+
|
|
349
404
|
return {
|
|
350
405
|
'url_for': url_for,
|
|
351
406
|
'iatoolkit_version': self.version,
|
|
352
407
|
'app_name': 'IAToolkit',
|
|
353
|
-
'
|
|
354
|
-
'
|
|
408
|
+
'user_identifier': SessionManager.get('user_identifier'),
|
|
409
|
+
'company_short_name': SessionManager.get('company_short_name'),
|
|
410
|
+
'user_is_local': user_profile.get('user_is_local'),
|
|
411
|
+
'user_email': user_profile.get('user_email'),
|
|
412
|
+
'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
|
|
413
|
+
'flashed_messages': get_flashed_messages(with_categories=True),
|
|
414
|
+
't': translate_for_template
|
|
355
415
|
}
|
|
356
416
|
|
|
357
417
|
def _get_default_static_folder(self) -> str:
|
|
358
418
|
try:
|
|
359
419
|
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src/iatoolkit
|
|
360
|
-
|
|
361
|
-
return os.path.join(src_dir, "static")
|
|
420
|
+
return os.path.join(current_dir, "static")
|
|
362
421
|
except:
|
|
363
422
|
return 'static'
|
|
364
423
|
|
|
365
424
|
def _get_default_template_folder(self) -> str:
|
|
366
425
|
try:
|
|
367
426
|
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src/iatoolkit
|
|
368
|
-
|
|
369
|
-
return os.path.join(src_dir, "templates")
|
|
427
|
+
return os.path.join(current_dir, "templates")
|
|
370
428
|
except:
|
|
371
429
|
return 'templates'
|
|
372
430
|
|
|
@@ -380,7 +438,7 @@ class IAToolkit:
|
|
|
380
438
|
return self._injector
|
|
381
439
|
|
|
382
440
|
def get_dispatcher(self):
|
|
383
|
-
from services.dispatcher_service import Dispatcher
|
|
441
|
+
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
384
442
|
if not self._injector:
|
|
385
443
|
raise IAToolkitException(
|
|
386
444
|
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
@@ -396,18 +454,33 @@ class IAToolkit:
|
|
|
396
454
|
)
|
|
397
455
|
return self.db_manager
|
|
398
456
|
|
|
457
|
+
def _setup_download_dir(self):
|
|
458
|
+
# 1. set the default download directory
|
|
459
|
+
default_download_dir = os.path.join(os.getcwd(), 'iatoolkit-downloads')
|
|
460
|
+
|
|
461
|
+
# 3. if user specified one, use it
|
|
462
|
+
download_dir = self.app.config.get('IATOOLKIT_DOWNLOAD_DIR', default_download_dir)
|
|
463
|
+
|
|
464
|
+
# 3. save it in the app config
|
|
465
|
+
self.app.config['IATOOLKIT_DOWNLOAD_DIR'] = download_dir
|
|
466
|
+
|
|
467
|
+
# 4. make sure the directory exists
|
|
468
|
+
try:
|
|
469
|
+
os.makedirs(download_dir, exist_ok=True)
|
|
470
|
+
except OSError as e:
|
|
471
|
+
raise IAToolkitException(
|
|
472
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
473
|
+
"No se pudo crear el directorio de descarga. Verifique que el directorio existe y tenga permisos de escritura."
|
|
474
|
+
)
|
|
475
|
+
logging.info(f"✅ download dir created in: {download_dir}")
|
|
476
|
+
|
|
399
477
|
|
|
400
478
|
def current_iatoolkit() -> IAToolkit:
|
|
401
479
|
return IAToolkit.get_instance()
|
|
402
480
|
|
|
403
|
-
#
|
|
481
|
+
# Función de conveniencia para inicialización rápida
|
|
404
482
|
def create_app(config: Optional[Dict[str, Any]] = None) -> Flask:
|
|
405
483
|
toolkit = IAToolkit(config)
|
|
406
484
|
toolkit.create_iatoolkit()
|
|
407
485
|
|
|
408
486
|
return toolkit.app
|
|
409
|
-
|
|
410
|
-
if __name__ == "__main__":
|
|
411
|
-
app = create_app()
|
|
412
|
-
if app:
|
|
413
|
-
app.run(debug=True)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
7
|
+
from injector import inject
|
|
8
|
+
# call_service.py
|
|
9
|
+
import requests
|
|
10
|
+
from typing import Optional, Dict, Any, Tuple, Union
|
|
11
|
+
from requests import RequestException
|
|
12
|
+
|
|
13
|
+
class CallServiceClient:
|
|
14
|
+
@inject
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.headers = {'Content-Type': 'application/json'}
|
|
17
|
+
|
|
18
|
+
def _merge_headers(self, extra: Optional[Dict[str, str]]) -> Dict[str, str]:
|
|
19
|
+
if not extra:
|
|
20
|
+
return dict(self.headers)
|
|
21
|
+
merged = dict(self.headers)
|
|
22
|
+
merged.update(extra)
|
|
23
|
+
return merged
|
|
24
|
+
|
|
25
|
+
def _normalize_timeout(self, timeout: Union[int, float, Tuple[int, int], Tuple[float, float]]) -> Tuple[float, float]:
|
|
26
|
+
# Si pasan un solo número → (connect, read) = (10, timeout)
|
|
27
|
+
if isinstance(timeout, (int, float)):
|
|
28
|
+
return (10, float(timeout))
|
|
29
|
+
return (float(timeout[0]), float(timeout[1]))
|
|
30
|
+
|
|
31
|
+
def _deserialize_response(self, response) -> Tuple[Any, int]:
|
|
32
|
+
try:
|
|
33
|
+
return response.json(), response.status_code
|
|
34
|
+
except ValueError:
|
|
35
|
+
# No es JSON → devolver texto
|
|
36
|
+
return response.text, response.status_code
|
|
37
|
+
|
|
38
|
+
def get(
|
|
39
|
+
self,
|
|
40
|
+
endpoint: str,
|
|
41
|
+
params: Optional[Dict[str, Any]] = None,
|
|
42
|
+
headers: Optional[Dict[str, str]] = None,
|
|
43
|
+
timeout: Union[int, float, Tuple[int, int]] = 10
|
|
44
|
+
):
|
|
45
|
+
try:
|
|
46
|
+
response = requests.get(
|
|
47
|
+
endpoint,
|
|
48
|
+
params=params,
|
|
49
|
+
headers=self._merge_headers(headers),
|
|
50
|
+
timeout=self._normalize_timeout(timeout)
|
|
51
|
+
)
|
|
52
|
+
except RequestException as e:
|
|
53
|
+
raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
|
|
54
|
+
return self._deserialize_response(response)
|
|
55
|
+
|
|
56
|
+
def post(
|
|
57
|
+
self,
|
|
58
|
+
endpoint: str,
|
|
59
|
+
json_dict: Optional[Dict[str, Any]] = None,
|
|
60
|
+
params: Optional[Dict[str, Any]] = None,
|
|
61
|
+
headers: Optional[Dict[str, str]] = None,
|
|
62
|
+
timeout: Union[int, float, Tuple[int, int]] = 10
|
|
63
|
+
):
|
|
64
|
+
try:
|
|
65
|
+
response = requests.post(
|
|
66
|
+
endpoint,
|
|
67
|
+
params=params,
|
|
68
|
+
json=json_dict,
|
|
69
|
+
headers=self._merge_headers(headers),
|
|
70
|
+
timeout=self._normalize_timeout(timeout)
|
|
71
|
+
)
|
|
72
|
+
except RequestException as e:
|
|
73
|
+
raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
|
|
74
|
+
return self._deserialize_response(response)
|
|
75
|
+
|
|
76
|
+
def put(
|
|
77
|
+
self,
|
|
78
|
+
endpoint: str,
|
|
79
|
+
json_dict: Optional[Dict[str, Any]] = None,
|
|
80
|
+
params: Optional[Dict[str, Any]] = None,
|
|
81
|
+
headers: Optional[Dict[str, str]] = None,
|
|
82
|
+
timeout: Union[int, float, Tuple[int, int]] = 10
|
|
83
|
+
):
|
|
84
|
+
try:
|
|
85
|
+
response = requests.put(
|
|
86
|
+
endpoint,
|
|
87
|
+
params=params,
|
|
88
|
+
json=json_dict,
|
|
89
|
+
headers=self._merge_headers(headers),
|
|
90
|
+
timeout=self._normalize_timeout(timeout)
|
|
91
|
+
)
|
|
92
|
+
except RequestException as e:
|
|
93
|
+
raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
|
|
94
|
+
return self._deserialize_response(response)
|
|
95
|
+
|
|
96
|
+
def delete(
|
|
97
|
+
self,
|
|
98
|
+
endpoint: str,
|
|
99
|
+
json_dict: Optional[Dict[str, Any]] = None,
|
|
100
|
+
params: Optional[Dict[str, Any]] = None,
|
|
101
|
+
headers: Optional[Dict[str, str]] = None,
|
|
102
|
+
timeout: Union[int, float, Tuple[int, int]] = 10
|
|
103
|
+
):
|
|
104
|
+
try:
|
|
105
|
+
response = requests.delete(
|
|
106
|
+
endpoint,
|
|
107
|
+
params=params,
|
|
108
|
+
json=json_dict,
|
|
109
|
+
headers=self._merge_headers(headers),
|
|
110
|
+
timeout=self._normalize_timeout(timeout)
|
|
111
|
+
)
|
|
112
|
+
except RequestException as e:
|
|
113
|
+
raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
|
|
114
|
+
return self._deserialize_response(response)
|
|
115
|
+
|
|
116
|
+
def post_files(
|
|
117
|
+
self,
|
|
118
|
+
endpoint: str,
|
|
119
|
+
data: Dict[str, Any],
|
|
120
|
+
params: Optional[Dict[str, Any]] = None,
|
|
121
|
+
headers: Optional[Dict[str, str]] = None,
|
|
122
|
+
timeout: Union[int, float, Tuple[int, int]] = 10
|
|
123
|
+
):
|
|
124
|
+
# Para multipart/form-data no imponemos Content-Type por defecto
|
|
125
|
+
merged_headers = dict(self.headers)
|
|
126
|
+
merged_headers.pop('Content-Type', None)
|
|
127
|
+
if headers:
|
|
128
|
+
merged_headers.update(headers)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
response = requests.post(
|
|
132
|
+
endpoint,
|
|
133
|
+
params=params,
|
|
134
|
+
files=data,
|
|
135
|
+
headers=merged_headers,
|
|
136
|
+
timeout=self._normalize_timeout(timeout)
|
|
137
|
+
)
|
|
138
|
+
except RequestException as e:
|
|
139
|
+
raise IAToolkitException(IAToolkitException.ErrorType.REQUEST_ERROR, str(e))
|
|
140
|
+
return self._deserialize_response(response)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileConnector(ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def list_files(self) -> List[str]:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def get_file_content(self, file_path: str) -> bytes:
|
|
17
|
+
pass
|