iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__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 +27 -35
- iatoolkit/base_company.py +3 -35
- iatoolkit/cli_commands.py +18 -47
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +48 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +39 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +138 -0
- iatoolkit/common/session_manager.py +26 -0
- iatoolkit/common/util.py +353 -0
- iatoolkit/company_registry.py +66 -29
- iatoolkit/core.py +514 -0
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/brevo_mail_app.py +123 -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/google_chat_app.py +57 -0
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
- iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
- iatoolkit/infra/llm_proxy.py +268 -0
- iatoolkit/infra/llm_response.py +45 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +222 -0
- iatoolkit/locales/es.yaml +225 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +187 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +105 -0
- iatoolkit/repositories/models.py +279 -0
- iatoolkit/repositories/profile_repo.py +171 -0
- iatoolkit/repositories/vs_repo.py +150 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +7 -7
- iatoolkit/services/branding_service.py +153 -0
- iatoolkit/services/company_context_service.py +214 -0
- iatoolkit/services/configuration_service.py +375 -0
- iatoolkit/services/dispatcher_service.py +134 -0
- {services → iatoolkit/services}/document_service.py +20 -8
- iatoolkit/services/embedding_service.py +148 -0
- iatoolkit/services/excel_service.py +156 -0
- {services → iatoolkit/services}/file_processor_service.py +36 -21
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +80 -0
- iatoolkit/services/language_service.py +89 -0
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/services/llm_client_service.py +438 -0
- iatoolkit/services/load_documents_service.py +174 -0
- iatoolkit/services/mail_service.py +213 -0
- {services → iatoolkit/services}/profile_service.py +200 -101
- iatoolkit/services/prompt_service.py +303 -0
- iatoolkit/services/query_service.py +467 -0
- iatoolkit/services/search_service.py +55 -0
- iatoolkit/services/sql_service.py +169 -0
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +117 -0
- iatoolkit/services/user_session_context_service.py +213 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +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 +110 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +401 -0
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +103 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +38 -0
- iatoolkit/static/styles/chat_iatoolkit.css +559 -0
- iatoolkit/static/styles/chat_modal.css +133 -0
- iatoolkit/static/styles/chat_public.css +135 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +398 -0
- iatoolkit/static/styles/llm_output.css +148 -0
- iatoolkit/static/styles/onboarding.css +176 -0
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +30 -23
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +45 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +78 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +337 -0
- iatoolkit/templates/chat_modals.html +185 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +51 -0
- iatoolkit/templates/onboarding_shell.html +106 -0
- iatoolkit/templates/signup.html +79 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +96 -0
- iatoolkit/views/change_password_view.py +116 -0
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +75 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +63 -0
- iatoolkit/views/init_context_api_view.py +74 -0
- iatoolkit/views/llmquery_api_view.py +59 -0
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/load_document_api_view.py +65 -0
- iatoolkit/views/login_view.py +170 -0
- iatoolkit/views/logout_api_view.py +57 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +100 -0
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +60 -0
- iatoolkit-0.107.4.dist-info/METADATA +268 -0
- iatoolkit-0.107.4.dist-info/RECORD +132 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
- iatoolkit/iatoolkit.py +0 -413
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.3.9.dist-info/METADATA +0 -252
- iatoolkit-0.3.9.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/dispatcher_service.py +0 -351
- services/excel_service.py +0 -98
- services/history_service.py +0 -45
- services/jwt_service.py +0 -91
- services/load_documents_service.py +0 -212
- services/mail_service.py +0 -62
- services/prompt_manager_service.py +0 -172
- services/query_service.py +0 -334
- services/search_service.py +0 -32
- services/sql_service.py +0 -42
- services/tasks_service.py +0 -188
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import request, jsonify
|
|
7
|
+
from flask.views import MethodView
|
|
8
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
9
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
10
|
+
from iatoolkit.services.auth_service import AuthService
|
|
11
|
+
from injector import inject
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HelpContentApiView(MethodView):
|
|
16
|
+
"""
|
|
17
|
+
Handles requests from the web UI to fetch a user's query history.
|
|
18
|
+
Authentication is based on the active Flask session.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@inject
|
|
22
|
+
def __init__(self,
|
|
23
|
+
auth_service: AuthService,
|
|
24
|
+
config_service: ConfigurationService,
|
|
25
|
+
i18n_service: I18nService):
|
|
26
|
+
self.auth_service = auth_service
|
|
27
|
+
self.config_service = config_service
|
|
28
|
+
self.i18n_service = i18n_service
|
|
29
|
+
|
|
30
|
+
def post(self, company_short_name: str):
|
|
31
|
+
try:
|
|
32
|
+
# 1. Get the authenticated user's
|
|
33
|
+
auth_result = self.auth_service.verify()
|
|
34
|
+
if not auth_result.get("success"):
|
|
35
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
36
|
+
|
|
37
|
+
user_identifier = auth_result.get('user_identifier')
|
|
38
|
+
|
|
39
|
+
# 2. Call the config service with the unified identifier.
|
|
40
|
+
response = self.config_service.get_configuration(
|
|
41
|
+
company_short_name=company_short_name,
|
|
42
|
+
content_key='help_content' # specific key for this service
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if "error" in response:
|
|
46
|
+
# Handle errors reported by the service itself.
|
|
47
|
+
return jsonify({'error_message': response["error"]}), 400
|
|
48
|
+
|
|
49
|
+
return jsonify(response), 200
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logging.exception(
|
|
53
|
+
f"Unexpected error fetching help_content for {company_short_name}: {e}")
|
|
54
|
+
return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import request, jsonify
|
|
7
|
+
from flask.views import MethodView
|
|
8
|
+
from iatoolkit.services.history_manager_service import HistoryManagerService
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
11
|
+
from injector import inject
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HistoryApiView(MethodView):
|
|
16
|
+
"""
|
|
17
|
+
Handles requests from the web UI to fetch a user's query history.
|
|
18
|
+
Authentication is based on the active Flask session.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@inject
|
|
22
|
+
def __init__(self,
|
|
23
|
+
auth_service: AuthService,
|
|
24
|
+
history_service: HistoryManagerService,
|
|
25
|
+
i18n_service: I18nService):
|
|
26
|
+
self.auth_service = auth_service
|
|
27
|
+
self.history_service = history_service
|
|
28
|
+
self.i18n_service = i18n_service
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def post(self, company_short_name: str):
|
|
32
|
+
try:
|
|
33
|
+
# 1. Get the authenticated user's
|
|
34
|
+
auth_result = self.auth_service.verify()
|
|
35
|
+
if not auth_result.get("success"):
|
|
36
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
37
|
+
|
|
38
|
+
user_identifier = auth_result.get('user_identifier')
|
|
39
|
+
|
|
40
|
+
# 2. Call the history service with the unified identifier.
|
|
41
|
+
# The service's signature should now only expect user_identifier.
|
|
42
|
+
response = self.history_service.get_full_history(
|
|
43
|
+
company_short_name=company_short_name,
|
|
44
|
+
user_identifier=user_identifier
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if "error" in response:
|
|
48
|
+
# Handle errors reported by the service itself.
|
|
49
|
+
return jsonify({'error_message': response["error"]}), 400
|
|
50
|
+
|
|
51
|
+
return jsonify(response), 200
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.exception(
|
|
55
|
+
f"Unexpected error: {e}")
|
|
56
|
+
return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# iatoolkit/views/home_view.py
|
|
2
|
+
from flask import render_template, render_template_string, request
|
|
3
|
+
from flask.views import MethodView
|
|
4
|
+
from injector import inject
|
|
5
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
6
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
7
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
8
|
+
from iatoolkit.common.util import Utility
|
|
9
|
+
|
|
10
|
+
class HomeView(MethodView):
|
|
11
|
+
"""
|
|
12
|
+
Handles the rendering of the company-specific home page with a login widget.
|
|
13
|
+
If the custom template is not found or fails, it renders an error page.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
profile_service: ProfileService,
|
|
19
|
+
branding_service: BrandingService,
|
|
20
|
+
i18n_service: I18nService,
|
|
21
|
+
utility: Utility):
|
|
22
|
+
self.profile_service = profile_service
|
|
23
|
+
self.branding_service = branding_service
|
|
24
|
+
self.i18n_service = i18n_service
|
|
25
|
+
self.util = utility
|
|
26
|
+
|
|
27
|
+
def get(self, company_short_name: str):
|
|
28
|
+
branding_data = {}
|
|
29
|
+
try:
|
|
30
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
31
|
+
if not company:
|
|
32
|
+
return render_template('error.html',
|
|
33
|
+
message=self.i18n_service.t('errors.templates.company_not_found')), 404
|
|
34
|
+
|
|
35
|
+
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
36
|
+
|
|
37
|
+
template_name = self.util.get_template_by_language("home")
|
|
38
|
+
home_template = self.util.get_company_template(company_short_name, template_name)
|
|
39
|
+
|
|
40
|
+
# 2. Verificamos si el archivo de plantilla personalizado no existe.
|
|
41
|
+
if not home_template:
|
|
42
|
+
message = self.i18n_service.t('errors.templates.home_template_not_found', company_name=company_short_name)
|
|
43
|
+
return render_template(
|
|
44
|
+
"error.html",
|
|
45
|
+
company_short_name=company_short_name,
|
|
46
|
+
branding=branding_data,
|
|
47
|
+
message=message
|
|
48
|
+
), 500
|
|
49
|
+
|
|
50
|
+
# 3. Si el archivo existe, intentamos leerlo y renderizarlo.
|
|
51
|
+
return render_template_string(
|
|
52
|
+
home_template,
|
|
53
|
+
company_short_name=company_short_name,
|
|
54
|
+
branding=branding_data,
|
|
55
|
+
)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
|
|
58
|
+
return render_template(
|
|
59
|
+
"error.html",
|
|
60
|
+
company_short_name=company_short_name,
|
|
61
|
+
branding=branding_data,
|
|
62
|
+
message=message
|
|
63
|
+
), 500
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from flask.views import MethodView
|
|
2
|
+
from injector import inject
|
|
3
|
+
from iatoolkit.services.query_service import QueryService
|
|
4
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
5
|
+
from iatoolkit.services.auth_service import AuthService
|
|
6
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
7
|
+
from flask import jsonify, request
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InitContextApiView(MethodView):
|
|
12
|
+
"""
|
|
13
|
+
API endpoint to force a full context rebuild for a user.
|
|
14
|
+
Handles both web users (via session) and API users (via API Key).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@inject
|
|
18
|
+
def __init__(self,
|
|
19
|
+
auth_service: AuthService,
|
|
20
|
+
query_service: QueryService,
|
|
21
|
+
profile_service: ProfileService,
|
|
22
|
+
i18n_service: I18nService):
|
|
23
|
+
self.auth_service = auth_service
|
|
24
|
+
self.query_service = query_service
|
|
25
|
+
self.profile_service = profile_service
|
|
26
|
+
self.i18n_service = i18n_service
|
|
27
|
+
|
|
28
|
+
def post(self, company_short_name: str):
|
|
29
|
+
"""
|
|
30
|
+
Cleans and rebuilds the context. The user is identified either by
|
|
31
|
+
an active web session or by the external_user_id in the JSON payload
|
|
32
|
+
for API calls.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
# 1. Authenticate the request. This handles both session and API Key.
|
|
36
|
+
auth_result = self.auth_service.verify()
|
|
37
|
+
if not auth_result.get("success"):
|
|
38
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
39
|
+
|
|
40
|
+
user_identifier = auth_result.get('user_identifier')
|
|
41
|
+
|
|
42
|
+
# check if model was sent as a parameter
|
|
43
|
+
data = request.get_json(silent=True) or {}
|
|
44
|
+
model = data.get('model', '')
|
|
45
|
+
|
|
46
|
+
# reinit the LLM context
|
|
47
|
+
response = self.query_service.init_context(
|
|
48
|
+
company_short_name=company_short_name,
|
|
49
|
+
user_identifier=user_identifier,
|
|
50
|
+
model=model)
|
|
51
|
+
|
|
52
|
+
# Respond with JSON, as this is an API endpoint.
|
|
53
|
+
success_message = self.i18n_service.t('api_responses.context_reloaded_success')
|
|
54
|
+
response_message = {'status': 'OK', 'message': success_message}
|
|
55
|
+
|
|
56
|
+
# if received a response ID with the context, return it
|
|
57
|
+
if response and response.get('response_id'):
|
|
58
|
+
response_message['response_id'] = response['response_id']
|
|
59
|
+
|
|
60
|
+
return jsonify(response_message), 200
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logging.exception(f"errors while reloading context: {e}")
|
|
64
|
+
error_message = self.i18n_service.t('errors.general.unexpected_error', error=str(e))
|
|
65
|
+
return jsonify({"error_message": error_message}), 406
|
|
66
|
+
|
|
67
|
+
def options(self, company_short_name):
|
|
68
|
+
"""
|
|
69
|
+
Maneja las solicitudes preflight de CORS.
|
|
70
|
+
Su única función es existir y devolver una respuesta exitosa para que
|
|
71
|
+
el middleware Flask-CORS pueda interceptarla y añadir las cabeceras
|
|
72
|
+
'Access-Control-Allow-*'.
|
|
73
|
+
"""
|
|
74
|
+
return {}, 200
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from flask.views import MethodView
|
|
2
|
+
from flask import request, jsonify
|
|
3
|
+
from injector import inject
|
|
4
|
+
from iatoolkit.services.query_service import QueryService
|
|
5
|
+
from iatoolkit.services.auth_service import AuthService
|
|
6
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
7
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
class LLMQueryApiView(MethodView):
|
|
11
|
+
"""
|
|
12
|
+
API-only endpoint for submitting queries. Authenticates via API Key.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@inject
|
|
16
|
+
def __init__(self,
|
|
17
|
+
auth_service: AuthService,
|
|
18
|
+
query_service: QueryService,
|
|
19
|
+
profile_service: ProfileService,
|
|
20
|
+
i18n_service: I18nService):
|
|
21
|
+
self.auth_service = auth_service
|
|
22
|
+
self.query_service = query_service
|
|
23
|
+
self.profile_service = profile_service
|
|
24
|
+
self.i18n_service = i18n_service
|
|
25
|
+
|
|
26
|
+
def post(self, company_short_name: str):
|
|
27
|
+
try:
|
|
28
|
+
# 1. Authenticate the API request.
|
|
29
|
+
auth_result = self.auth_service.verify()
|
|
30
|
+
if not auth_result.get("success"):
|
|
31
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
32
|
+
|
|
33
|
+
# 2. Get the user identifier from the payload.
|
|
34
|
+
user_identifier = auth_result.get('user_identifier')
|
|
35
|
+
|
|
36
|
+
data = request.get_json()
|
|
37
|
+
if not data:
|
|
38
|
+
return jsonify({"error": "Invalid JSON body"}), 400
|
|
39
|
+
|
|
40
|
+
# 4. Call the unified query service method.
|
|
41
|
+
result = self.query_service.llm_query(
|
|
42
|
+
company_short_name=company_short_name,
|
|
43
|
+
user_identifier=user_identifier,
|
|
44
|
+
model=data.get('model', ''),
|
|
45
|
+
question=data.get('question', ''),
|
|
46
|
+
prompt_name=data.get('prompt_name'),
|
|
47
|
+
client_data=data.get('client_data', {}),
|
|
48
|
+
ignore_history=data.get('ignore_history', False),
|
|
49
|
+
files=data.get('files', [])
|
|
50
|
+
)
|
|
51
|
+
if 'error' in result:
|
|
52
|
+
return jsonify(result), 409
|
|
53
|
+
|
|
54
|
+
return jsonify(result), 200
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logging.exception(
|
|
58
|
+
f"Unexpected error: {e}")
|
|
59
|
+
return jsonify({"error": True, "error_message": self.i18n_service.t('errors.general.unexpected_error')}), 500
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask.views import MethodView
|
|
7
|
+
from flask import redirect, url_for, jsonify, request, g
|
|
8
|
+
from injector import inject
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
class LoadCompanyConfigurationApiView(MethodView):
|
|
15
|
+
@inject
|
|
16
|
+
def __init__(self,
|
|
17
|
+
configuration_service: ConfigurationService,
|
|
18
|
+
profile_service: ProfileService,
|
|
19
|
+
auth_service: AuthService):
|
|
20
|
+
self.configuration_service = configuration_service
|
|
21
|
+
self.profile_service = profile_service
|
|
22
|
+
self.auth_service = auth_service
|
|
23
|
+
|
|
24
|
+
def get(self, company_short_name: str = None):
|
|
25
|
+
try:
|
|
26
|
+
# 1. Get the authenticated user's
|
|
27
|
+
auth_result = self.auth_service.verify(anonymous=True)
|
|
28
|
+
if not auth_result.get("success"):
|
|
29
|
+
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
30
|
+
|
|
31
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
32
|
+
if not company:
|
|
33
|
+
return jsonify({"error": "company not found."}), 404
|
|
34
|
+
|
|
35
|
+
config, errors = self.configuration_service.load_configuration(company_short_name)
|
|
36
|
+
if config:
|
|
37
|
+
self.configuration_service.register_data_sources(company_short_name)
|
|
38
|
+
|
|
39
|
+
# this is fo avoid serialization issues
|
|
40
|
+
if 'company' in config:
|
|
41
|
+
config.pop('company')
|
|
42
|
+
|
|
43
|
+
status_code = 200 if not errors else 400
|
|
44
|
+
return {'config': config, 'errors': [errors]}, status_code
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logging.exception(f"Unexpected error: {e}")
|
|
47
|
+
return {'status': 'error'}, 500
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask.views import MethodView
|
|
7
|
+
from flask import request, jsonify
|
|
8
|
+
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
11
|
+
from injector import inject
|
|
12
|
+
import base64
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LoadDocumentApiView(MethodView):
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
auth_service: AuthService,
|
|
19
|
+
doc_service: LoadDocumentsService,
|
|
20
|
+
profile_repo: ProfileRepo,):
|
|
21
|
+
self.auth_service = auth_service
|
|
22
|
+
self.doc_service = doc_service
|
|
23
|
+
self.profile_repo = profile_repo
|
|
24
|
+
|
|
25
|
+
def post(self):
|
|
26
|
+
try:
|
|
27
|
+
# 1. Authenticate the API request.
|
|
28
|
+
auth_result = self.auth_service.verify()
|
|
29
|
+
if not auth_result.get("success"):
|
|
30
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
31
|
+
|
|
32
|
+
req_data = request.get_json()
|
|
33
|
+
required_fields = ['company', 'filename', 'content']
|
|
34
|
+
for field in required_fields:
|
|
35
|
+
if field not in req_data:
|
|
36
|
+
return jsonify({"error": f"El campo {field} es requerido"}), 400
|
|
37
|
+
|
|
38
|
+
company_short_name = req_data.get('company', '')
|
|
39
|
+
filename = req_data.get('filename', False)
|
|
40
|
+
base64_content = req_data.get('content', '')
|
|
41
|
+
metadata = req_data.get('metadata', {})
|
|
42
|
+
|
|
43
|
+
# get company
|
|
44
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
45
|
+
if not company:
|
|
46
|
+
return jsonify({"error": f"La empresa {company_short_name} no existe"}), 400
|
|
47
|
+
|
|
48
|
+
# get the file content from base64
|
|
49
|
+
content = base64.b64decode(base64_content)
|
|
50
|
+
|
|
51
|
+
new_document = self.doc_service._file_processing_callback(
|
|
52
|
+
filename=filename,
|
|
53
|
+
content=content,
|
|
54
|
+
company=company,
|
|
55
|
+
context={'metadata': metadata})
|
|
56
|
+
|
|
57
|
+
return jsonify({
|
|
58
|
+
"document_id": new_document.id,
|
|
59
|
+
}), 200
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
response = jsonify({"error": str(e)})
|
|
63
|
+
response.status_code = 500
|
|
64
|
+
|
|
65
|
+
return response
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask.views import MethodView
|
|
7
|
+
from flask import (request, redirect, render_template, url_for,
|
|
8
|
+
render_template_string, flash, make_response)
|
|
9
|
+
from injector import inject
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.jwt_service import JWTService
|
|
12
|
+
from iatoolkit.services.query_service import QueryService
|
|
13
|
+
from iatoolkit.services.prompt_service import PromptService
|
|
14
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
15
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
16
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
17
|
+
from iatoolkit.views.base_login_view import BaseLoginView
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LoginView(BaseLoginView):
|
|
22
|
+
"""
|
|
23
|
+
Handles login for local users.
|
|
24
|
+
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
25
|
+
"""
|
|
26
|
+
def post(self, company_short_name: str):
|
|
27
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
28
|
+
if not company:
|
|
29
|
+
return render_template('error.html',
|
|
30
|
+
message=self.i18n_service.t('errors.templates.company_not_found')), 404
|
|
31
|
+
|
|
32
|
+
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
33
|
+
email = request.form.get('email')
|
|
34
|
+
password = request.form.get('password')
|
|
35
|
+
current_lang = request.form.get('lang') or request.args.get('lang') or 'en'
|
|
36
|
+
|
|
37
|
+
# 1. Authenticate internal user
|
|
38
|
+
auth_response = self.auth_service.login_local_user(
|
|
39
|
+
company_short_name=company_short_name,
|
|
40
|
+
email=email,
|
|
41
|
+
password=password
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if not auth_response['success']:
|
|
45
|
+
flash(auth_response["message"], 'error')
|
|
46
|
+
|
|
47
|
+
# Resolve the correct template name based on language (e.g., home_en.html or home_es.html)
|
|
48
|
+
template_name = self.utility.get_template_by_language("home")
|
|
49
|
+
home_template = self.utility.get_company_template(company_short_name, template_name)
|
|
50
|
+
|
|
51
|
+
if not home_template:
|
|
52
|
+
return render_template('error.html',
|
|
53
|
+
message=f'Home template ({template_name}) not found.'), 500
|
|
54
|
+
|
|
55
|
+
return render_template_string(
|
|
56
|
+
home_template,
|
|
57
|
+
company_short_name=company_short_name,
|
|
58
|
+
company=company,
|
|
59
|
+
branding=branding_data,
|
|
60
|
+
form_data={"email": email},
|
|
61
|
+
), 400
|
|
62
|
+
|
|
63
|
+
user_identifier = auth_response['user_identifier']
|
|
64
|
+
|
|
65
|
+
# 3. define URL to call when slow path is finished
|
|
66
|
+
target_url = url_for('finalize_no_token',
|
|
67
|
+
company_short_name=company_short_name,
|
|
68
|
+
_external=True,
|
|
69
|
+
lang=current_lang)
|
|
70
|
+
|
|
71
|
+
# 2. Delegate the path decision to the centralized logic.
|
|
72
|
+
try:
|
|
73
|
+
return self._handle_login_path(company_short_name, user_identifier, target_url)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
|
|
76
|
+
return render_template(
|
|
77
|
+
"error.html",
|
|
78
|
+
company_short_name=company_short_name,
|
|
79
|
+
branding=branding_data,
|
|
80
|
+
message=message
|
|
81
|
+
), 500
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class FinalizeContextView(MethodView):
|
|
85
|
+
"""
|
|
86
|
+
Finalizes context loading in the slow path.
|
|
87
|
+
This view is invoked by the iframe inside onboarding_shell.html.
|
|
88
|
+
"""
|
|
89
|
+
@inject
|
|
90
|
+
def __init__(self,
|
|
91
|
+
profile_service: ProfileService,
|
|
92
|
+
query_service: QueryService,
|
|
93
|
+
prompt_service: PromptService,
|
|
94
|
+
branding_service: BrandingService,
|
|
95
|
+
config_service: ConfigurationService,
|
|
96
|
+
jwt_service: JWTService,
|
|
97
|
+
i18n_service: I18nService
|
|
98
|
+
):
|
|
99
|
+
self.profile_service = profile_service
|
|
100
|
+
self.jwt_service = jwt_service
|
|
101
|
+
self.query_service = query_service
|
|
102
|
+
self.prompt_service = prompt_service
|
|
103
|
+
self.branding_service = branding_service
|
|
104
|
+
self.config_service = config_service
|
|
105
|
+
self.i18n_service = i18n_service
|
|
106
|
+
|
|
107
|
+
def get(self, company_short_name: str, token: str = None):
|
|
108
|
+
try:
|
|
109
|
+
# get the languaje from the query string if it exists
|
|
110
|
+
current_lang = request.args.get('lang') or 'en'
|
|
111
|
+
|
|
112
|
+
session_info = self.profile_service.get_current_session_info()
|
|
113
|
+
if session_info:
|
|
114
|
+
# session exists, internal user
|
|
115
|
+
user_identifier = session_info.get('user_identifier')
|
|
116
|
+
token = ''
|
|
117
|
+
elif token:
|
|
118
|
+
# user identified by api-key
|
|
119
|
+
payload = self.jwt_service.validate_chat_jwt(token)
|
|
120
|
+
if not payload:
|
|
121
|
+
logging.warning("Fallo crítico: No se pudo leer el auth token.")
|
|
122
|
+
return redirect(url_for('home', company_short_name=company_short_name, lang=current_lang))
|
|
123
|
+
|
|
124
|
+
user_identifier = payload.get('user_identifier')
|
|
125
|
+
else:
|
|
126
|
+
logging.error("missing session information or auth token")
|
|
127
|
+
return redirect(url_for('home', company_short_name=company_short_name, lang=current_lang))
|
|
128
|
+
|
|
129
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
130
|
+
if not company:
|
|
131
|
+
return render_template('error.html',
|
|
132
|
+
company_short_name=company_short_name,
|
|
133
|
+
message="Empresa no encontrada"), 404
|
|
134
|
+
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
135
|
+
|
|
136
|
+
default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
|
|
137
|
+
|
|
138
|
+
# 2. Finalize the context rebuild (the heavy task).
|
|
139
|
+
self.query_service.set_context_for_llm(
|
|
140
|
+
company_short_name=company_short_name,
|
|
141
|
+
user_identifier=user_identifier
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# 3. render the chat page.
|
|
145
|
+
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
146
|
+
onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
|
|
147
|
+
|
|
148
|
+
# Get the entire 'js_messages' block in the correct language.
|
|
149
|
+
js_translations = self.i18n_service.get_translation_block('js_messages')
|
|
150
|
+
|
|
151
|
+
# Importante: no envolver con make_response; dejar que Flask gestione
|
|
152
|
+
# tanto strings como tuplas (string, status) que pueda devolver render_template
|
|
153
|
+
return render_template(
|
|
154
|
+
"chat.html",
|
|
155
|
+
company_short_name=company_short_name,
|
|
156
|
+
user_identifier=user_identifier,
|
|
157
|
+
branding=branding_data,
|
|
158
|
+
prompts=prompts,
|
|
159
|
+
onboarding_cards=onboarding_cards,
|
|
160
|
+
js_translations=js_translations,
|
|
161
|
+
redeem_token=token,
|
|
162
|
+
llm_default_model=default_llm_model,
|
|
163
|
+
llm_available_models=available_llm_models,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return render_template("error.html",
|
|
168
|
+
company_short_name=company_short_name,
|
|
169
|
+
branding=branding_data,
|
|
170
|
+
message=f"An unexpected error occurred during context loading: {str(e)}"), 500
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask.views import MethodView
|
|
7
|
+
from flask import redirect, url_for, jsonify, request, g
|
|
8
|
+
from injector import inject
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.common.session_manager import SessionManager
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
class LogoutApiView(MethodView):
|
|
15
|
+
@inject
|
|
16
|
+
def __init__(self,
|
|
17
|
+
profile_service: ProfileService,
|
|
18
|
+
auth_service: AuthService):
|
|
19
|
+
self.profile_service = profile_service
|
|
20
|
+
self.auth_service = auth_service
|
|
21
|
+
|
|
22
|
+
def get(self, company_short_name: str = None):
|
|
23
|
+
try:
|
|
24
|
+
# 1. Get the authenticated user's
|
|
25
|
+
auth_result = self.auth_service.verify(anonymous=True)
|
|
26
|
+
if not auth_result.get("success"):
|
|
27
|
+
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
28
|
+
|
|
29
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
30
|
+
if not company:
|
|
31
|
+
return jsonify({"error": "company not found."}), 404
|
|
32
|
+
|
|
33
|
+
# get URL for redirection
|
|
34
|
+
url_for_redirect = company.parameters.get('external_urls', {}).get('logout_url')
|
|
35
|
+
if not url_for_redirect:
|
|
36
|
+
current_lang = (
|
|
37
|
+
request.args.get('lang')
|
|
38
|
+
or getattr(g, 'lang', None)
|
|
39
|
+
or 'en'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
url_for_redirect = url_for('home',
|
|
43
|
+
company_short_name=company_short_name,
|
|
44
|
+
lang=current_lang)
|
|
45
|
+
|
|
46
|
+
# clear de session cookie
|
|
47
|
+
SessionManager.clear()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
'status': 'success',
|
|
51
|
+
'url': url_for_redirect,
|
|
52
|
+
}, 200
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.exception(f"Unexpected error: {e}")
|
|
55
|
+
return {'status': 'error'}, 500
|
|
56
|
+
|
|
57
|
+
|