iatoolkit 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/__init__.py +41 -0
- iatoolkit/base_company.py +42 -0
- iatoolkit/company_registry.py +98 -0
- iatoolkit/iatoolkit.py +405 -0
- iatoolkit/toolkit_config.py +13 -0
- iatoolkit-0.3.1.dist-info/METADATA +252 -0
- iatoolkit-0.3.1.dist-info/RECORD +28 -0
- iatoolkit-0.3.1.dist-info/WHEEL +5 -0
- iatoolkit-0.3.1.dist-info/top_level.txt +2 -0
- services/__init__.py +5 -0
- services/api_service.py +30 -0
- services/benchmark_service.py +139 -0
- services/dispatcher_service.py +312 -0
- services/document_service.py +159 -0
- services/excel_service.py +98 -0
- services/file_processor_service.py +92 -0
- services/history_service.py +45 -0
- services/jwt_service.py +91 -0
- services/load_documents_service.py +212 -0
- services/mail_service.py +62 -0
- services/profile_service.py +376 -0
- services/prompt_manager_service.py +180 -0
- services/query_service.py +332 -0
- services/search_service.py +32 -0
- services/sql_service.py +42 -0
- services/tasks_service.py +188 -0
- services/user_feedback_service.py +67 -0
- services/user_session_context_service.py +85 -0
iatoolkit/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IAToolkit Package
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Expose main classes and functions at the top level of the package
|
|
6
|
+
|
|
7
|
+
# Assuming 'toolkit.py' contains the IAToolkit class
|
|
8
|
+
from .iatoolkit import IAToolkit, create_app
|
|
9
|
+
from .iatoolkit import current_iatoolkit
|
|
10
|
+
|
|
11
|
+
# Assuming 'app_factory.py' contains create_app and register_company
|
|
12
|
+
from .company_registry import register_company
|
|
13
|
+
|
|
14
|
+
# Assuming 'base_company.py' contains BaseCompany
|
|
15
|
+
from .base_company import BaseCompany
|
|
16
|
+
|
|
17
|
+
# --- Services ---
|
|
18
|
+
# Assuming they are in a 'services' sub-package
|
|
19
|
+
from services.sql_service import SqlService
|
|
20
|
+
from services.excel_service import ExcelService
|
|
21
|
+
from services.dispatcher_service import Dispatcher
|
|
22
|
+
from services.document_service import DocumentService
|
|
23
|
+
from services.search_service import SearchService
|
|
24
|
+
from repositories.profile_repo import ProfileRepo
|
|
25
|
+
from repositories.database_manager import DatabaseManager
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'IAToolkit',
|
|
30
|
+
'create_app',
|
|
31
|
+
'current_iatoolkit',
|
|
32
|
+
'register_company',
|
|
33
|
+
'BaseCompany',
|
|
34
|
+
'SqlService',
|
|
35
|
+
'ExcelService',
|
|
36
|
+
'Dispatcher',
|
|
37
|
+
'DocumentService',
|
|
38
|
+
'SearchService',
|
|
39
|
+
'ProfileRepo',
|
|
40
|
+
'DatabaseManager',
|
|
41
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Producto: IAToolkit
|
|
3
|
+
# Todos los derechos reservados.
|
|
4
|
+
# En trámite de registro en el Registro de Propiedad Intelectual de Chile.
|
|
5
|
+
|
|
6
|
+
# companies/base_company.py
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseCompany(ABC):
|
|
12
|
+
def __init__(self, profile_repo: Any = None, llm_query_repo: Any = None):
|
|
13
|
+
self.profile_repo = profile_repo
|
|
14
|
+
self.llm_query_repo = llm_query_repo
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
# initialize all the database tables needed
|
|
18
|
+
def init_db(self):
|
|
19
|
+
raise NotImplementedError("La subclase debe implementar el método init_db()")
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
# get context specific for this company
|
|
23
|
+
def get_company_context(self, **kwargs) -> str:
|
|
24
|
+
raise NotImplementedError("La subclase debe implementar el método get_company_context()")
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
# execute the specific action configured in the intent table
|
|
28
|
+
def handle_request(self, tag: str, params: dict) -> dict:
|
|
29
|
+
raise NotImplementedError("La subclase debe implementar el método handle_request()")
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
# get context specific for the query
|
|
33
|
+
def start_execution(self):
|
|
34
|
+
raise NotImplementedError("La subclase debe implementar el método start_execution()")
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
# get context specific for the query
|
|
38
|
+
def get_metadata_from_filename(self, filename: str) -> dict:
|
|
39
|
+
raise NotImplementedError("La subclase debe implementar el método get_query_context()")
|
|
40
|
+
|
|
41
|
+
def unsupported_operation(self, tag):
|
|
42
|
+
raise NotImplementedError(f"La operación '{tag}' no está soportada por esta empresa.")
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Producto: IAToolkit
|
|
3
|
+
|
|
4
|
+
from typing import Dict, Type, Any
|
|
5
|
+
from .base_company import BaseCompany
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CompanyRegistry:
|
|
10
|
+
"""
|
|
11
|
+
Registro centralizado de empresas para iatoolkit.
|
|
12
|
+
|
|
13
|
+
Permite a los clientes registrar sus clases de empresa de forma explícita
|
|
14
|
+
en lugar de usar autodiscovery.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._company_classes: Dict[str, Type[BaseCompany]] = {}
|
|
19
|
+
self._company_instances: Dict[str, BaseCompany] = {}
|
|
20
|
+
self._injector = None
|
|
21
|
+
|
|
22
|
+
def register_company(self, name: str, company_class: Type[BaseCompany]) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Registra una clase de empresa.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: Nombre de la empresa (ej: 'maxxa')
|
|
28
|
+
company_class: Clase que hereda de BaseCompany
|
|
29
|
+
"""
|
|
30
|
+
if not issubclass(company_class, BaseCompany):
|
|
31
|
+
raise ValueError(f"La clase {company_class.__name__} debe heredar de BaseCompany")
|
|
32
|
+
|
|
33
|
+
company_key = name.lower()
|
|
34
|
+
self._company_classes[company_key] = company_class
|
|
35
|
+
|
|
36
|
+
logging.info(f"Empresa registrada: {company_key} -> {company_class.__name__}")
|
|
37
|
+
|
|
38
|
+
def set_injector(self, injector) -> None:
|
|
39
|
+
"""Establece el injector para crear instancias con dependencias"""
|
|
40
|
+
self._injector = injector
|
|
41
|
+
|
|
42
|
+
def instantiate_companies(self) -> Dict[str, BaseCompany]:
|
|
43
|
+
"""
|
|
44
|
+
Instancia todas las empresas registradas con inyección de dependencias.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dict con instancias de empresas {name: instance}
|
|
48
|
+
"""
|
|
49
|
+
if not self._injector:
|
|
50
|
+
raise RuntimeError("Injector no configurado. Llame a set_injector() primero.")
|
|
51
|
+
|
|
52
|
+
for company_key, company_class in self._company_classes.items():
|
|
53
|
+
if company_key not in self._company_instances:
|
|
54
|
+
try:
|
|
55
|
+
# use de injector to create the instance
|
|
56
|
+
company_instance = self._injector.get(company_class)
|
|
57
|
+
self._company_instances[company_key] = company_instance
|
|
58
|
+
logging.info(f"company '{company_key}' created in dispatcher")
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logging.error(f"Error instanciando empresa {company_key}: {e}")
|
|
62
|
+
logging.exception(e)
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
return self._company_instances.copy()
|
|
66
|
+
|
|
67
|
+
def get_registered_companies(self) -> Dict[str, Type[BaseCompany]]:
|
|
68
|
+
"""Retorna las clases registradas"""
|
|
69
|
+
return self._company_classes.copy()
|
|
70
|
+
|
|
71
|
+
def get_company_instances(self) -> Dict[str, BaseCompany]:
|
|
72
|
+
"""Retorna las instancias de empresas"""
|
|
73
|
+
return self._company_instances.copy()
|
|
74
|
+
|
|
75
|
+
def clear(self) -> None:
|
|
76
|
+
"""Limpia el registro (útil para tests)"""
|
|
77
|
+
self._company_classes.clear()
|
|
78
|
+
self._company_instances.clear()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Instancia global del registry
|
|
82
|
+
_company_registry = CompanyRegistry()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def register_company(name: str, company_class: Type[BaseCompany]) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Función pública para registrar empresas.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
name: Nombre de la empresa
|
|
91
|
+
company_class: Clase que hereda de BaseCompany
|
|
92
|
+
"""
|
|
93
|
+
_company_registry.register_company(name, company_class)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_company_registry() -> CompanyRegistry:
|
|
97
|
+
"""Obtiene el registry global"""
|
|
98
|
+
return _company_registry
|
iatoolkit/iatoolkit.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Producto: IAToolkit Core
|
|
3
|
+
# Framework opensource para chatbots empresariales con IA
|
|
4
|
+
|
|
5
|
+
from flask import Flask, url_for, current_app
|
|
6
|
+
from flask_session import Session
|
|
7
|
+
from flask_injector import FlaskInjector
|
|
8
|
+
from flask_bcrypt import Bcrypt
|
|
9
|
+
from flask_cors import CORS
|
|
10
|
+
from common.auth import IAuthentication
|
|
11
|
+
from common.util import Utility
|
|
12
|
+
from common.exceptions import IAToolkitException
|
|
13
|
+
from common.session_manager import SessionManager
|
|
14
|
+
from urllib.parse import urlparse
|
|
15
|
+
import redis
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import click
|
|
19
|
+
from typing import Optional, Dict, Any
|
|
20
|
+
from repositories.database_manager import DatabaseManager
|
|
21
|
+
from injector import Binder, singleton, Injector
|
|
22
|
+
from .toolkit_config import IAToolkitConfig
|
|
23
|
+
|
|
24
|
+
VERSION = "2.0.0"
|
|
25
|
+
|
|
26
|
+
# global variable for yhe unique instance of IAToolkit
|
|
27
|
+
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class IAToolkit:
|
|
31
|
+
"""
|
|
32
|
+
IAToolkit main class
|
|
33
|
+
"""
|
|
34
|
+
def __new__(cls, config: Optional[Dict[str, Any]] = None):
|
|
35
|
+
"""
|
|
36
|
+
Implementa el patrón Singleton
|
|
37
|
+
"""
|
|
38
|
+
global _iatoolkit_instance
|
|
39
|
+
if _iatoolkit_instance is None:
|
|
40
|
+
_iatoolkit_instance = super().__new__(cls)
|
|
41
|
+
_iatoolkit_instance._initialized = False
|
|
42
|
+
return _iatoolkit_instance
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
46
|
+
"""
|
|
47
|
+
Args:
|
|
48
|
+
config: Diccionario opcional de configuración que sobrescribe variables de entorno
|
|
49
|
+
"""
|
|
50
|
+
if self._initialized:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
self.config = config or {}
|
|
54
|
+
self.app: Optional[Flask] = None
|
|
55
|
+
self.db_manager: Optional[DatabaseManager] = None
|
|
56
|
+
self._injector: Optional[Injector] = None
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def get_instance(cls) -> 'IAToolkit':
|
|
60
|
+
"""
|
|
61
|
+
Obtiene la instancia única de IAToolkit
|
|
62
|
+
"""
|
|
63
|
+
global _iatoolkit_instance
|
|
64
|
+
if _iatoolkit_instance is None:
|
|
65
|
+
_iatoolkit_instance = cls()
|
|
66
|
+
return _iatoolkit_instance
|
|
67
|
+
|
|
68
|
+
def create_iatoolkit(self):
|
|
69
|
+
"""
|
|
70
|
+
Creates, configures, and returns the Flask application instance.
|
|
71
|
+
his is the main entry point for the application factory.
|
|
72
|
+
"""
|
|
73
|
+
self._setup_logging()
|
|
74
|
+
|
|
75
|
+
# Step 1: Create the Flask app instance
|
|
76
|
+
self._create_flask_instance()
|
|
77
|
+
|
|
78
|
+
# Step 2: Set up the core components that DI depends on
|
|
79
|
+
self._setup_database()
|
|
80
|
+
|
|
81
|
+
# Step 3: Create the Injector with CORE dependencies (NO VIEWS)
|
|
82
|
+
toolkit_config_module = IAToolkitConfig(app=self.app, db_manager=self.db_manager)
|
|
83
|
+
self._injector = Injector([
|
|
84
|
+
toolkit_config_module,
|
|
85
|
+
self._configure_core_dependencies # This method binds services, repos, etc.
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
# Step 4: Register routes using the fully configured injector
|
|
89
|
+
self._register_routes()
|
|
90
|
+
|
|
91
|
+
# Step 5: Initialize FlaskInjector. This is now primarily for request-scoped injections
|
|
92
|
+
# and other integrations, as views are handled manually.
|
|
93
|
+
FlaskInjector(app=self.app, injector=self._injector)
|
|
94
|
+
|
|
95
|
+
# Step 6: Finalize setup within the application context
|
|
96
|
+
self._setup_redis_sessions()
|
|
97
|
+
self._setup_cors()
|
|
98
|
+
self._setup_additional_services()
|
|
99
|
+
self._setup_cli_commands()
|
|
100
|
+
self._setup_context_processors()
|
|
101
|
+
|
|
102
|
+
logging.info(f"🎉 IAToolkit v{VERSION} inicializado correctamente")
|
|
103
|
+
return self.app
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_config_value(self, key: str, default=None):
|
|
107
|
+
"""Obtiene un valor de configuración, primero del dict config, luego de env vars"""
|
|
108
|
+
return self.config.get(key, os.getenv(key, default))
|
|
109
|
+
|
|
110
|
+
def _setup_logging(self):
|
|
111
|
+
log_level_str = self._get_config_value('FLASK_ENV', 'production')
|
|
112
|
+
log_level = logging.INFO if log_level_str in ('dev', 'development') else logging.WARNING
|
|
113
|
+
|
|
114
|
+
logging.basicConfig(
|
|
115
|
+
level=log_level,
|
|
116
|
+
format="%(asctime)s - IATOOLKIT - %(name)s - %(levelname)s - %(message)s",
|
|
117
|
+
handlers=[logging.StreamHandler()],
|
|
118
|
+
force=True
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _register_routes(self):
|
|
122
|
+
"""Registers routes by passing the configured injector."""
|
|
123
|
+
from common.routes import register_views
|
|
124
|
+
|
|
125
|
+
# Pass the injector to the view registration function
|
|
126
|
+
register_views(self._injector, self.app)
|
|
127
|
+
|
|
128
|
+
logging.info("✅ Routes registered.")
|
|
129
|
+
|
|
130
|
+
def _create_flask_instance(self):
|
|
131
|
+
static_folder = self._get_config_value('STATIC_FOLDER') or self._get_default_static_folder()
|
|
132
|
+
template_folder = self._get_config_value('TEMPLATE_FOLDER') or self._get_default_template_folder()
|
|
133
|
+
|
|
134
|
+
self.app = Flask(__name__,
|
|
135
|
+
static_folder=static_folder,
|
|
136
|
+
template_folder=template_folder)
|
|
137
|
+
|
|
138
|
+
is_https = self._get_config_value('USE_HTTPS', 'false').lower() == 'true'
|
|
139
|
+
is_dev = self._get_config_value('FLASK_ENV') == 'development'
|
|
140
|
+
|
|
141
|
+
self.app.config.update({
|
|
142
|
+
'VERSION': VERSION,
|
|
143
|
+
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
144
|
+
'SESSION_COOKIE_SAMESITE': "None" if is_https else "Lax",
|
|
145
|
+
'SESSION_COOKIE_SECURE': is_https,
|
|
146
|
+
'SESSION_PERMANENT': False,
|
|
147
|
+
'SESSION_USE_SIGNER': True,
|
|
148
|
+
'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
|
|
149
|
+
'JWT_ALGORITHM': 'HS256',
|
|
150
|
+
'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
# Configuración para tokenizers en desarrollo
|
|
154
|
+
if is_dev:
|
|
155
|
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
156
|
+
|
|
157
|
+
def _setup_database(self):
|
|
158
|
+
database_uri = self._get_config_value('DATABASE_URI')
|
|
159
|
+
if not database_uri:
|
|
160
|
+
raise IAToolkitException(
|
|
161
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
162
|
+
"DATABASE_URI es requerida (config dict o variable de entorno)"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
self.db_manager = DatabaseManager(database_uri)
|
|
166
|
+
self.db_manager.create_all()
|
|
167
|
+
logging.info("✅ Base de datos configurada correctamente")
|
|
168
|
+
|
|
169
|
+
def _setup_redis_sessions(self):
|
|
170
|
+
redis_url = self._get_config_value('REDIS_URL')
|
|
171
|
+
if not redis_url:
|
|
172
|
+
logging.warning("⚠️ REDIS_URL no configurada, usando sesiones en memoria")
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
url = urlparse(redis_url)
|
|
177
|
+
redis_instance = redis.Redis(
|
|
178
|
+
host=url.hostname,
|
|
179
|
+
port=url.port,
|
|
180
|
+
password=url.password,
|
|
181
|
+
ssl=(url.scheme == "rediss"),
|
|
182
|
+
ssl_cert_reqs=None
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
self.app.config.update({
|
|
186
|
+
'SESSION_TYPE': 'redis',
|
|
187
|
+
'SESSION_REDIS': redis_instance
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
Session(self.app)
|
|
191
|
+
logging.info("✅ Redis y sesiones configurados correctamente")
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logging.error(f"❌ Error configurando Redis: {e}")
|
|
195
|
+
logging.warning("⚠️ Continuando sin Redis")
|
|
196
|
+
|
|
197
|
+
def _setup_cors(self):
|
|
198
|
+
"""🌐 Configura CORS"""
|
|
199
|
+
# Origins por defecto para desarrollo
|
|
200
|
+
default_origins = [
|
|
201
|
+
"http://localhost:3000",
|
|
202
|
+
"http://localhost:5001",
|
|
203
|
+
"http://127.0.0.1:5001"
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
# Obtener origins adicionales desde configuración/env
|
|
207
|
+
extra_origins = []
|
|
208
|
+
for i in range(1, 11): # Soporte para CORS_ORIGIN_1 a CORS_ORIGIN_10
|
|
209
|
+
origin = self._get_config_value(f'CORS_ORIGIN_{i}')
|
|
210
|
+
if origin:
|
|
211
|
+
extra_origins.append(origin)
|
|
212
|
+
|
|
213
|
+
all_origins = default_origins + extra_origins
|
|
214
|
+
|
|
215
|
+
CORS(self.app,
|
|
216
|
+
supports_credentials=True,
|
|
217
|
+
origins=all_origins,
|
|
218
|
+
allow_headers=[
|
|
219
|
+
"Content-Type", "Authorization", "X-Requested-With",
|
|
220
|
+
"X-Chat-Token", "x-chat-token"
|
|
221
|
+
],
|
|
222
|
+
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
|
|
223
|
+
|
|
224
|
+
logging.info(f"✅ CORS configurado para: {all_origins}")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _configure_core_dependencies(self, binder: Binder):
|
|
228
|
+
"""⚙️ Configures all system dependencies."""
|
|
229
|
+
try:
|
|
230
|
+
# Core dependencies
|
|
231
|
+
binder.bind(Injector, to=self._injector, scope=singleton)
|
|
232
|
+
|
|
233
|
+
# Bind all application components by calling the specific methods
|
|
234
|
+
self._bind_repositories(binder)
|
|
235
|
+
self._bind_services(binder)
|
|
236
|
+
self._bind_infrastructure(binder)
|
|
237
|
+
self._bind_views(binder)
|
|
238
|
+
|
|
239
|
+
logging.info("✅ Dependencias configuradas correctamente")
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
logging.error(f"❌ Error configurando dependencias: {e}")
|
|
243
|
+
raise IAToolkitException(
|
|
244
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
245
|
+
f"❌ Error configurando dependencias: {e}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _bind_repositories(self, binder: Binder):
|
|
249
|
+
from repositories.document_repo import DocumentRepo
|
|
250
|
+
from repositories.document_type_repo import DocumentTypeRepo
|
|
251
|
+
from repositories.profile_repo import ProfileRepo
|
|
252
|
+
from repositories.llm_query_repo import LLMQueryRepo
|
|
253
|
+
from repositories.vs_repo import VSRepo
|
|
254
|
+
from repositories.tasks_repo import TaskRepo
|
|
255
|
+
|
|
256
|
+
binder.bind(DocumentRepo, to=DocumentRepo)
|
|
257
|
+
binder.bind(DocumentTypeRepo, to=DocumentTypeRepo)
|
|
258
|
+
binder.bind(ProfileRepo, to=ProfileRepo)
|
|
259
|
+
binder.bind(LLMQueryRepo, to=LLMQueryRepo)
|
|
260
|
+
binder.bind(VSRepo, to=VSRepo)
|
|
261
|
+
binder.bind(TaskRepo, to=TaskRepo)
|
|
262
|
+
|
|
263
|
+
def _bind_services(self, binder: Binder):
|
|
264
|
+
from services.query_service import QueryService
|
|
265
|
+
from services.tasks_service import TaskService
|
|
266
|
+
from services.benchmark_service import BenchmarkService
|
|
267
|
+
from services.document_service import DocumentService
|
|
268
|
+
from services.prompt_manager_service import PromptService
|
|
269
|
+
from services.excel_service import ExcelService
|
|
270
|
+
from services.mail_service import MailService
|
|
271
|
+
from services.load_documents_service import LoadDocumentsService
|
|
272
|
+
from services.profile_service import ProfileService
|
|
273
|
+
from services.jwt_service import JWTService
|
|
274
|
+
from services.dispatcher_service import Dispatcher
|
|
275
|
+
|
|
276
|
+
binder.bind(QueryService, to=QueryService)
|
|
277
|
+
binder.bind(TaskService, to=TaskService)
|
|
278
|
+
binder.bind(BenchmarkService, to=BenchmarkService)
|
|
279
|
+
binder.bind(DocumentService, to=DocumentService)
|
|
280
|
+
binder.bind(PromptService, to=PromptService)
|
|
281
|
+
binder.bind(ExcelService, to=ExcelService)
|
|
282
|
+
binder.bind(MailService, to=MailService)
|
|
283
|
+
binder.bind(LoadDocumentsService, to=LoadDocumentsService)
|
|
284
|
+
binder.bind(ProfileService, to=ProfileService)
|
|
285
|
+
binder.bind(JWTService, to=JWTService)
|
|
286
|
+
binder.bind(Dispatcher, to=Dispatcher, scope=singleton)
|
|
287
|
+
|
|
288
|
+
def _bind_infrastructure(self, binder: Binder):
|
|
289
|
+
from infra.llm_client import llmClient
|
|
290
|
+
from infra.llm_proxy import LLMProxy
|
|
291
|
+
from infra.google_chat_app import GoogleChatApp
|
|
292
|
+
from infra.mail_app import MailApp
|
|
293
|
+
|
|
294
|
+
binder.bind(LLMProxy, to=LLMProxy, scope=singleton)
|
|
295
|
+
binder.bind(llmClient, to=llmClient, scope=singleton)
|
|
296
|
+
binder.bind(GoogleChatApp, to=GoogleChatApp)
|
|
297
|
+
binder.bind(MailApp, to=MailApp)
|
|
298
|
+
binder.bind(IAuthentication, to=IAuthentication)
|
|
299
|
+
binder.bind(Utility, to=Utility)
|
|
300
|
+
|
|
301
|
+
def _bind_views(self, binder: Binder):
|
|
302
|
+
"""Vincula las vistas después de que el injector ha sido creado"""
|
|
303
|
+
from views.llmquery_view import LLMQueryView
|
|
304
|
+
from views.home_view import HomeView
|
|
305
|
+
from views.chat_view import ChatView
|
|
306
|
+
from views.change_password_view import ChangePasswordView
|
|
307
|
+
|
|
308
|
+
binder.bind(HomeView, to=HomeView)
|
|
309
|
+
binder.bind(ChatView, to=ChatView)
|
|
310
|
+
binder.bind(ChangePasswordView, to=ChangePasswordView)
|
|
311
|
+
binder.bind(LLMQueryView, to=LLMQueryView)
|
|
312
|
+
|
|
313
|
+
logging.info("✅ Views configuradas correctamente")
|
|
314
|
+
|
|
315
|
+
def _setup_additional_services(self):
|
|
316
|
+
Bcrypt(self.app)
|
|
317
|
+
|
|
318
|
+
def _setup_cli_commands(self):
|
|
319
|
+
"""⌨️ Configura comandos CLI básicos"""
|
|
320
|
+
|
|
321
|
+
@self.app.cli.command("init-db")
|
|
322
|
+
def init_db():
|
|
323
|
+
"""🗄️ Inicializa la base de datos del sistema"""
|
|
324
|
+
try:
|
|
325
|
+
from services.dispatcher_service import Dispatcher
|
|
326
|
+
dispatcher = self._get_injector().get(Dispatcher)
|
|
327
|
+
|
|
328
|
+
click.echo("🚀 Inicializando base de datos...")
|
|
329
|
+
dispatcher.init_db()
|
|
330
|
+
click.echo("✅ Base de datos inicializada correctamente")
|
|
331
|
+
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logging.exception(e)
|
|
334
|
+
click.echo(f"❌ Error: {e}")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _setup_context_processors(self):
|
|
338
|
+
# Configura context processors para templates
|
|
339
|
+
@self.app.context_processor
|
|
340
|
+
def inject_globals():
|
|
341
|
+
return {
|
|
342
|
+
'url_for': url_for,
|
|
343
|
+
'iatoolkit_version': VERSION,
|
|
344
|
+
'app_name': 'IAToolkit',
|
|
345
|
+
'user': SessionManager.get('user'),
|
|
346
|
+
'user_company': SessionManager.get('company_short_name'),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
def _get_default_static_folder(self) -> str:
|
|
350
|
+
try:
|
|
351
|
+
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src/iatoolkit
|
|
352
|
+
src_dir = os.path.dirname(current_dir) # .../src
|
|
353
|
+
return os.path.join(src_dir, "static")
|
|
354
|
+
except:
|
|
355
|
+
return 'static'
|
|
356
|
+
|
|
357
|
+
def _get_default_template_folder(self) -> str:
|
|
358
|
+
try:
|
|
359
|
+
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src/iatoolkit
|
|
360
|
+
src_dir = os.path.dirname(current_dir) # .../src
|
|
361
|
+
return os.path.join(src_dir, "templates")
|
|
362
|
+
except:
|
|
363
|
+
return 'templates'
|
|
364
|
+
|
|
365
|
+
def _get_injector(self) -> Injector:
|
|
366
|
+
"""Obtiene el injector actual"""
|
|
367
|
+
if not self._injector:
|
|
368
|
+
raise IAToolkitException(
|
|
369
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
370
|
+
f"❌ injector not initialized"
|
|
371
|
+
)
|
|
372
|
+
return self._injector
|
|
373
|
+
|
|
374
|
+
def get_dispatcher(self):
|
|
375
|
+
from services.dispatcher_service import Dispatcher
|
|
376
|
+
if not self._injector:
|
|
377
|
+
raise IAToolkitException(
|
|
378
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
379
|
+
"App no inicializada. Llame a create_app() primero"
|
|
380
|
+
)
|
|
381
|
+
return self._injector.get(Dispatcher)
|
|
382
|
+
|
|
383
|
+
def get_database_manager(self) -> DatabaseManager:
|
|
384
|
+
if not self.db_manager:
|
|
385
|
+
raise IAToolkitException(
|
|
386
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
387
|
+
"Database manager no inicializado"
|
|
388
|
+
)
|
|
389
|
+
return self.db_manager
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def current_iatoolkit() -> IAToolkit:
|
|
393
|
+
return IAToolkit.get_instance()
|
|
394
|
+
|
|
395
|
+
# 🚀 Función de conveniencia para inicialización rápida
|
|
396
|
+
def create_app(config: Optional[Dict[str, Any]] = None) -> Flask:
|
|
397
|
+
toolkit = IAToolkit(config)
|
|
398
|
+
toolkit.create_iatoolkit()
|
|
399
|
+
|
|
400
|
+
return toolkit.app
|
|
401
|
+
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
app = create_app()
|
|
404
|
+
if app:
|
|
405
|
+
app.run(debug=True)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# src/iatoolkit/toolkit_config.py
|
|
2
|
+
from injector import Module, Binder, singleton
|
|
3
|
+
from flask import Flask
|
|
4
|
+
from repositories.database_manager import DatabaseManager
|
|
5
|
+
|
|
6
|
+
class IAToolkitConfig(Module):
|
|
7
|
+
def __init__(self, app: Flask, db_manager: DatabaseManager):
|
|
8
|
+
self.app = app
|
|
9
|
+
self.db_manager = db_manager
|
|
10
|
+
|
|
11
|
+
def configure(self, binder: Binder):
|
|
12
|
+
binder.bind(Flask, to=self.app, scope=singleton)
|
|
13
|
+
binder.bind(DatabaseManager, to=self.db_manager, scope=singleton)
|