iatoolkit 0.8.1__py3-none-any.whl → 0.63.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 +8 -34
- iatoolkit/base_company.py +14 -3
- iatoolkit/common/routes.py +83 -52
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +0 -27
- iatoolkit/iatoolkit.py +61 -46
- iatoolkit/infra/llm_client.py +7 -8
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/repositories/database_manager.py +17 -2
- iatoolkit/repositories/models.py +31 -6
- iatoolkit/repositories/profile_repo.py +7 -2
- iatoolkit/services/auth_service.py +188 -0
- iatoolkit/services/branding_service.py +147 -0
- iatoolkit/services/dispatcher_service.py +10 -40
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/history_service.py +3 -12
- iatoolkit/services/jwt_service.py +15 -24
- iatoolkit/services/onboarding_service.py +43 -0
- iatoolkit/services/profile_service.py +97 -44
- iatoolkit/services/query_service.py +124 -81
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +67 -31
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
- iatoolkit/static/js/chat_history_button.js +126 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +130 -220
- 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 +52 -0
- iatoolkit/static/styles/chat_iatoolkit.css +329 -507
- iatoolkit/static/styles/chat_modal.css +95 -56
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +40 -0
- iatoolkit/templates/base.html +8 -3
- iatoolkit/templates/change_password.html +54 -37
- iatoolkit/templates/chat.html +149 -66
- iatoolkit/templates/chat_modals.html +47 -18
- iatoolkit/templates/error.html +41 -8
- iatoolkit/templates/forgot_password.html +37 -24
- iatoolkit/templates/index.html +140 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +105 -0
- iatoolkit/templates/signup.html +64 -66
- iatoolkit/views/base_login_view.py +81 -0
- iatoolkit/views/change_password_view.py +23 -12
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
- iatoolkit/views/forgot_password_view.py +23 -13
- iatoolkit/views/history_api_view.py +52 -0
- iatoolkit/views/home_view.py +58 -25
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +68 -0
- iatoolkit/views/llmquery_api_view.py +45 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +118 -34
- iatoolkit/views/logout_api_view.py +45 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
- iatoolkit/views/signup_view.py +38 -29
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
- iatoolkit/views/verify_user_view.py +13 -8
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
- iatoolkit-0.63.4.dist-info/RECORD +113 -0
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/templates/home.html +0 -201
- iatoolkit/templates/login.html +0 -43
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -51
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -88
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit-0.8.1.dist-info/RECORD +0 -175
- tests/__init__.py +0 -5
- tests/common/__init__.py +0 -0
- tests/common/test_auth.py +0 -279
- tests/common/test_routes.py +0 -42
- tests/common/test_session_manager.py +0 -59
- tests/common/test_util.py +0 -444
- tests/companies/__init__.py +0 -5
- tests/conftest.py +0 -36
- tests/infra/__init__.py +0 -5
- tests/infra/connectors/__init__.py +0 -5
- tests/infra/connectors/test_google_drive_connector.py +0 -107
- tests/infra/connectors/test_local_file_connector.py +0 -85
- tests/infra/connectors/test_s3_connector.py +0 -95
- tests/infra/test_call_service.py +0 -92
- tests/infra/test_database_manager.py +0 -59
- tests/infra/test_gemini_adapter.py +0 -137
- tests/infra/test_google_chat_app.py +0 -68
- tests/infra/test_llm_client.py +0 -165
- tests/infra/test_llm_proxy.py +0 -122
- tests/infra/test_mail_app.py +0 -94
- tests/infra/test_openai_adapter.py +0 -105
- tests/infra/test_redis_session_manager_service.py +0 -117
- tests/repositories/__init__.py +0 -5
- tests/repositories/test_database_manager.py +0 -87
- tests/repositories/test_document_repo.py +0 -76
- tests/repositories/test_llm_query_repo.py +0 -340
- tests/repositories/test_models.py +0 -38
- tests/repositories/test_profile_repo.py +0 -142
- tests/repositories/test_tasks_repo.py +0 -76
- tests/repositories/test_vs_repo.py +0 -107
- tests/services/__init__.py +0 -5
- tests/services/test_dispatcher_service.py +0 -274
- tests/services/test_document_service.py +0 -181
- tests/services/test_excel_service.py +0 -208
- tests/services/test_file_processor_service.py +0 -121
- tests/services/test_history_service.py +0 -164
- tests/services/test_jwt_service.py +0 -255
- tests/services/test_load_documents_service.py +0 -112
- tests/services/test_mail_service.py +0 -70
- tests/services/test_profile_service.py +0 -379
- tests/services/test_prompt_manager_service.py +0 -190
- tests/services/test_query_service.py +0 -243
- tests/services/test_search_service.py +0 -39
- tests/services/test_sql_service.py +0 -160
- tests/services/test_tasks_service.py +0 -252
- tests/services/test_user_feedback_service.py +0 -389
- tests/services/test_user_session_context_service.py +0 -132
- tests/views/__init__.py +0 -5
- tests/views/test_change_password_view.py +0 -191
- tests/views/test_chat_token_request_view.py +0 -188
- tests/views/test_chat_view.py +0 -98
- tests/views/test_download_file_view.py +0 -149
- tests/views/test_external_chat_login_view.py +0 -120
- tests/views/test_external_login_view.py +0 -102
- tests/views/test_file_store_view.py +0 -128
- tests/views/test_forgot_password_view.py +0 -142
- tests/views/test_history_view.py +0 -336
- tests/views/test_home_view.py +0 -61
- tests/views/test_llm_query_view.py +0 -154
- tests/views/test_login_view.py +0 -114
- tests/views/test_prompt_view.py +0 -111
- tests/views/test_signup_view.py +0 -140
- tests/views/test_tasks_review_view.py +0 -104
- tests/views/test_tasks_view.py +0 -130
- tests/views/test_user_feedback_view.py +0 -214
- tests/views/test_verify_user_view.py +0 -110
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
|
@@ -3,38 +3,71 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from injector import inject
|
|
8
|
-
from iatoolkit.common.auth import IAuthentication
|
|
9
|
-
from iatoolkit.services.query_service import QueryService
|
|
10
|
-
from flask import jsonify
|
|
6
|
+
import os
|
|
11
7
|
import logging
|
|
8
|
+
from flask import request, jsonify, url_for
|
|
9
|
+
from iatoolkit.views.base_login_view import BaseLoginView
|
|
12
10
|
|
|
13
|
-
class ExternalLoginView(MethodView):
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
class ExternalLoginView(BaseLoginView):
|
|
13
|
+
"""
|
|
14
|
+
Handles login for external users via API.
|
|
15
|
+
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
16
|
+
"""
|
|
17
|
+
def post(self, company_short_name: str):
|
|
18
|
+
# Authenticate the API call.
|
|
19
|
+
auth_result = self.auth_service.verify()
|
|
20
|
+
if not auth_result.get("success"):
|
|
21
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if not iaut.get("success"):
|
|
27
|
-
return jsonify(iaut), 401
|
|
23
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
24
|
+
if not company:
|
|
25
|
+
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
user_identifier = auth_result.get('user_identifier')
|
|
28
|
+
|
|
29
|
+
# 2. Create the external user session.
|
|
30
|
+
self.profile_service.create_external_user_profile_context(company, user_identifier)
|
|
31
|
+
|
|
32
|
+
# 3. create a redeem_token for create session at the end of the process
|
|
33
|
+
redeem_token = self.jwt_service.generate_chat_jwt(
|
|
34
|
+
company_short_name=company_short_name,
|
|
35
|
+
user_identifier=user_identifier,
|
|
36
|
+
expires_delta_seconds=300
|
|
37
|
+
)
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
if not redeem_token:
|
|
40
|
+
return jsonify({"error": "Error al generar el redeem_token para login externo."}), 403
|
|
41
|
+
|
|
42
|
+
# 4. define URL to call when slow path is finished
|
|
43
|
+
target_url = url_for('finalize_with_token',
|
|
44
|
+
company_short_name=company_short_name,
|
|
45
|
+
token=redeem_token,
|
|
46
|
+
_external=True)
|
|
47
|
+
|
|
48
|
+
# 5. Delegate the path decision to the centralized logic.
|
|
49
|
+
try:
|
|
50
|
+
return self._handle_login_path(company, user_identifier, target_url, redeem_token)
|
|
37
51
|
except Exception as e:
|
|
38
|
-
logging.exception(
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
|
|
53
|
+
return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RedeemTokenApiView(BaseLoginView):
|
|
57
|
+
# this endpoint is only used ONLY by chat_main.js to redeem a chat token
|
|
58
|
+
def post(self, company_short_name: str):
|
|
59
|
+
data = request.get_json()
|
|
60
|
+
if not data or 'token' not in data:
|
|
61
|
+
return jsonify({"error": "Falta token de validación"}), 400
|
|
62
|
+
|
|
63
|
+
# get the token and validate with auth service
|
|
64
|
+
token = data.get('token')
|
|
65
|
+
redeem_result = self.auth_service.redeem_token_for_session(
|
|
66
|
+
company_short_name=company_short_name,
|
|
67
|
+
token=token
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not redeem_result['success']:
|
|
71
|
+
return {"error": redeem_result['error']}, 401
|
|
72
|
+
|
|
73
|
+
return {"status": "ok"}, 200
|
|
@@ -6,23 +6,30 @@
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
7
|
from flask import request, jsonify
|
|
8
8
|
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
9
10
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
10
11
|
from injector import inject
|
|
11
12
|
import base64
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class
|
|
15
|
+
class FileStoreApiView(MethodView):
|
|
15
16
|
@inject
|
|
16
17
|
def __init__(self,
|
|
18
|
+
auth_service: AuthService,
|
|
17
19
|
doc_service: LoadDocumentsService,
|
|
18
20
|
profile_repo: ProfileRepo,):
|
|
21
|
+
self.auth_service = auth_service
|
|
19
22
|
self.doc_service = doc_service
|
|
20
23
|
self.profile_repo = profile_repo
|
|
21
24
|
|
|
22
25
|
def post(self):
|
|
23
26
|
try:
|
|
24
|
-
|
|
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")
|
|
25
31
|
|
|
32
|
+
req_data = request.get_json()
|
|
26
33
|
required_fields = ['company', 'filename', 'content']
|
|
27
34
|
for field in required_fields:
|
|
28
35
|
if field not in req_data:
|
|
@@ -4,33 +4,43 @@
|
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
|
-
from flask import render_template, request, url_for
|
|
7
|
+
from flask import render_template, request, url_for, redirect, session
|
|
8
8
|
from injector import inject
|
|
9
9
|
from iatoolkit.services.profile_service import ProfileService
|
|
10
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
10
11
|
from itsdangerous import URLSafeTimedSerializer
|
|
11
12
|
import os
|
|
12
13
|
|
|
13
14
|
class ForgotPasswordView(MethodView):
|
|
14
15
|
@inject
|
|
15
|
-
def __init__(self, profile_service: ProfileService
|
|
16
|
+
def __init__(self, profile_service: ProfileService,
|
|
17
|
+
branding_service: BrandingService):
|
|
16
18
|
self.profile_service = profile_service
|
|
19
|
+
self.branding_service = branding_service # 3. Guardar la instancia
|
|
17
20
|
self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
|
|
18
21
|
|
|
19
22
|
def get(self, company_short_name: str):
|
|
20
23
|
# get company info
|
|
21
24
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
22
25
|
if not company:
|
|
23
|
-
return render_template('error.html',
|
|
26
|
+
return render_template('error.html',
|
|
27
|
+
company_short_name=company_short_name,
|
|
28
|
+
message="Empresa no encontrada"), 404
|
|
24
29
|
|
|
30
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
25
31
|
return render_template('forgot_password.html',
|
|
26
32
|
company=company,
|
|
27
|
-
company_short_name=company_short_name
|
|
33
|
+
company_short_name=company_short_name,
|
|
34
|
+
branding=branding_data
|
|
28
35
|
)
|
|
29
36
|
|
|
30
37
|
def post(self, company_short_name: str):
|
|
31
38
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
32
39
|
if not company:
|
|
33
|
-
return render_template('error.html',
|
|
40
|
+
return render_template('error.html',
|
|
41
|
+
company_short_name=company_short_name,
|
|
42
|
+
message="Empresa no encontrada"), 404
|
|
43
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
34
44
|
|
|
35
45
|
try:
|
|
36
46
|
email = request.form.get('email')
|
|
@@ -47,18 +57,18 @@ class ForgotPasswordView(MethodView):
|
|
|
47
57
|
'forgot_password.html',
|
|
48
58
|
company=company,
|
|
49
59
|
company_short_name=company_short_name,
|
|
50
|
-
|
|
60
|
+
branding=branding_data,
|
|
61
|
+
form_data={"email": email},
|
|
51
62
|
alert_message=response["error"]), 400
|
|
52
63
|
|
|
64
|
+
# Guardamos el mensaje y el icono en la sesión manualmente
|
|
65
|
+
session['alert_message'] = "Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña."
|
|
66
|
+
session['alert_icon'] = "success"
|
|
67
|
+
return redirect(url_for('home', company_short_name=company_short_name))
|
|
53
68
|
|
|
54
|
-
return render_template('login.html',
|
|
55
|
-
company=company,
|
|
56
|
-
company_short_name=company_short_name,
|
|
57
|
-
alert_icon='success',
|
|
58
|
-
alert_message="Hemos enviado un enlace a tu correo para restablecer la contraseña.")
|
|
59
69
|
except Exception as e:
|
|
60
70
|
return render_template("error.html",
|
|
61
|
-
company=company,
|
|
62
71
|
company_short_name=company_short_name,
|
|
63
|
-
|
|
72
|
+
branding=branding_data,
|
|
73
|
+
message=f"Ha ocurrido un error inesperado: {str(e)}"), 500
|
|
64
74
|
|
|
@@ -0,0 +1,52 @@
|
|
|
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_service import HistoryService
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from injector import inject
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HistoryApiView(MethodView):
|
|
15
|
+
"""
|
|
16
|
+
Handles requests from the web UI to fetch a user's query history.
|
|
17
|
+
Authentication is based on the active Flask session.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@inject
|
|
21
|
+
def __init__(self,
|
|
22
|
+
auth_service: AuthService,
|
|
23
|
+
history_service: HistoryService):
|
|
24
|
+
self.auth_service = auth_service
|
|
25
|
+
self.history_service = history_service
|
|
26
|
+
|
|
27
|
+
def post(self, company_short_name: str):
|
|
28
|
+
# 1. Get the authenticated user's
|
|
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
|
+
user_identifier = auth_result.get('user_identifier')
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
# 2. Call the history service with the unified identifier.
|
|
37
|
+
# The service's signature should now only expect user_identifier.
|
|
38
|
+
response = self.history_service.get_history(
|
|
39
|
+
company_short_name=company_short_name,
|
|
40
|
+
user_identifier=user_identifier
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if "error" in response:
|
|
44
|
+
# Handle errors reported by the service itself.
|
|
45
|
+
return jsonify({'error_message': response["error"]}), 400
|
|
46
|
+
|
|
47
|
+
return jsonify(response), 200
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logging.exception(
|
|
51
|
+
f"Unexpected error fetching history for {company_short_name}/{user_identifier}: {e}")
|
|
52
|
+
return jsonify({"error_message": "Ha ocurrido un error inesperado en el servidor."}), 500
|
iatoolkit/views/home_view.py
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# iatoolkit/views/home_view.py
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from flask import render_template, abort, session, render_template_string
|
|
6
5
|
from flask.views import MethodView
|
|
7
|
-
from flask import render_template, request
|
|
8
6
|
from injector import inject
|
|
9
7
|
from iatoolkit.services.profile_service import ProfileService
|
|
10
|
-
import
|
|
11
|
-
|
|
8
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
9
|
+
import logging
|
|
12
10
|
|
|
13
11
|
class HomeView(MethodView):
|
|
12
|
+
"""
|
|
13
|
+
Handles the rendering of the company-specific home page with a login widget.
|
|
14
|
+
If the custom template is not found or fails, it renders an error page.
|
|
15
|
+
"""
|
|
16
|
+
|
|
14
17
|
@inject
|
|
15
18
|
def __init__(self,
|
|
16
|
-
profile_service: ProfileService
|
|
19
|
+
profile_service: ProfileService,
|
|
20
|
+
branding_service: BrandingService):
|
|
17
21
|
self.profile_service = profile_service
|
|
22
|
+
self.branding_service = branding_service
|
|
23
|
+
|
|
24
|
+
def get(self, company_short_name: str):
|
|
25
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
26
|
+
|
|
27
|
+
if not company:
|
|
28
|
+
return render_template('error.html', message="Empresa no encontrada"), 404
|
|
29
|
+
|
|
30
|
+
branding_data = self.branding_service.get_company_branding(company)
|
|
31
|
+
alert_message = session.pop('alert_message', None)
|
|
32
|
+
alert_icon = session.pop('alert_icon', 'error')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# 1. Construimos la ruta al archivo de plantilla específico de la empresa.
|
|
36
|
+
company_template_path = os.path.join(os.getcwd(), f'companies/{company_short_name}/templates/home.html')
|
|
37
|
+
|
|
38
|
+
# 2. Verificamos si el archivo de plantilla personalizado no existe.
|
|
39
|
+
if not os.path.exists(company_template_path):
|
|
40
|
+
return render_template(
|
|
41
|
+
"error.html",
|
|
42
|
+
company_short_name=company_short_name,
|
|
43
|
+
branding=branding_data,
|
|
44
|
+
message=f"La plantilla de la página de inicio para la empresa '{company_short_name}' no está configurada."
|
|
45
|
+
), 500
|
|
46
|
+
|
|
47
|
+
# 3. Si el archivo existe, intentamos leerlo y renderizarlo.
|
|
48
|
+
try:
|
|
49
|
+
with open(company_template_path, 'r') as f:
|
|
50
|
+
template_string = f.read()
|
|
18
51
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
# Usamos render_template_string, que entiende el contexto de Flask.
|
|
53
|
+
return render_template_string(
|
|
54
|
+
template_string,
|
|
55
|
+
company=company,
|
|
56
|
+
company_short_name=company_short_name,
|
|
57
|
+
branding=branding_data,
|
|
58
|
+
alert_message=alert_message,
|
|
59
|
+
alert_icon=alert_icon
|
|
60
|
+
)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return render_template(
|
|
63
|
+
"error.html",
|
|
64
|
+
company_short_name=company_short_name,
|
|
65
|
+
branding=branding_data,
|
|
66
|
+
message=f"Ocurrió un error al procesar la plantilla personalizada de la página de inicio: {str(e)}"
|
|
67
|
+
), 500
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# iatoolkit/views/index_view.py
|
|
2
|
+
|
|
3
|
+
from flask import render_template, session
|
|
4
|
+
from flask.views import MethodView
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class IndexView(MethodView):
|
|
8
|
+
"""
|
|
9
|
+
Handles the rendering of the generic landing page, which no longer depends
|
|
10
|
+
on a specific company.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def get(self):
|
|
14
|
+
return render_template('index.html')
|
|
@@ -0,0 +1,68 @@
|
|
|
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 flask import jsonify, request
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InitContextApiView(MethodView):
|
|
11
|
+
"""
|
|
12
|
+
API endpoint to force a full context rebuild for a user.
|
|
13
|
+
Handles both web users (via session) and API users (via API Key).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
auth_service: AuthService,
|
|
19
|
+
query_service: QueryService,
|
|
20
|
+
profile_service: ProfileService):
|
|
21
|
+
self.auth_service = auth_service
|
|
22
|
+
self.query_service = query_service
|
|
23
|
+
self.profile_service = profile_service
|
|
24
|
+
|
|
25
|
+
def post(self, company_short_name: str):
|
|
26
|
+
"""
|
|
27
|
+
Cleans and rebuilds the context. The user is identified either by
|
|
28
|
+
an active web session or by the external_user_id in the JSON payload
|
|
29
|
+
for API calls.
|
|
30
|
+
"""
|
|
31
|
+
# 1. Authenticate the request. This handles both session and API Key.
|
|
32
|
+
auth_result = self.auth_service.verify()
|
|
33
|
+
if not auth_result.get("success"):
|
|
34
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
35
|
+
|
|
36
|
+
user_identifier = auth_result.get('user_identifier')
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# 2. Execute the forced rebuild sequence using the unified identifier.
|
|
40
|
+
self.query_service.session_context.clear_all_context(company_short_name, user_identifier)
|
|
41
|
+
logging.info(f"Context for {company_short_name}/{user_identifier} has been cleared.")
|
|
42
|
+
|
|
43
|
+
# LLM context is clean, now we can load it again
|
|
44
|
+
self.query_service.prepare_context(
|
|
45
|
+
company_short_name=company_short_name,
|
|
46
|
+
user_identifier=user_identifier
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self.query_service.finalize_context_rebuild(
|
|
50
|
+
company_short_name=company_short_name,
|
|
51
|
+
user_identifier=user_identifier
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 3. Respond with JSON, as this is an API endpoint.
|
|
55
|
+
return jsonify({'status': 'OK', 'message': 'El contexto se ha recargado con éxito.'}), 200
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logging.exception(f"Error durante la recarga de contexto {user_identifier}: {e}")
|
|
59
|
+
return jsonify({"error_message": str(e)}), 500
|
|
60
|
+
|
|
61
|
+
def options(self, company_short_name):
|
|
62
|
+
"""
|
|
63
|
+
Maneja las solicitudes preflight de CORS.
|
|
64
|
+
Su única función es existir y devolver una respuesta exitosa para que
|
|
65
|
+
el middleware Flask-CORS pueda interceptarla y añadir las cabeceras
|
|
66
|
+
'Access-Control-Allow-*'.
|
|
67
|
+
"""
|
|
68
|
+
return {}, 200
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import logging
|
|
8
|
+
|
|
9
|
+
class LLMQueryApiView(MethodView):
|
|
10
|
+
"""
|
|
11
|
+
API-only endpoint for submitting queries. Authenticates via API Key.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@inject
|
|
15
|
+
def __init__(self, auth_service: AuthService, query_service: QueryService, profile_service: ProfileService):
|
|
16
|
+
self.auth_service = auth_service
|
|
17
|
+
self.query_service = query_service
|
|
18
|
+
self.profile_service = profile_service
|
|
19
|
+
|
|
20
|
+
def post(self, company_short_name: str):
|
|
21
|
+
# 1. Authenticate the API request.
|
|
22
|
+
auth_result = self.auth_service.verify()
|
|
23
|
+
if not auth_result.get("success"):
|
|
24
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
25
|
+
|
|
26
|
+
# 2. Get the user identifier from the payload.
|
|
27
|
+
user_identifier = auth_result.get('user_identifier')
|
|
28
|
+
|
|
29
|
+
data = request.get_json()
|
|
30
|
+
if not data:
|
|
31
|
+
return jsonify({"error": "Invalid JSON body"}), 400
|
|
32
|
+
|
|
33
|
+
# 4. Call the unified query service method.
|
|
34
|
+
result = self.query_service.llm_query(
|
|
35
|
+
company_short_name=company_short_name,
|
|
36
|
+
user_identifier=user_identifier,
|
|
37
|
+
question=data.get('question', ''),
|
|
38
|
+
prompt_name=data.get('prompt_name'),
|
|
39
|
+
client_data=data.get('client_data', {}),
|
|
40
|
+
files=data.get('files', [])
|
|
41
|
+
)
|
|
42
|
+
if 'error' in result:
|
|
43
|
+
return jsonify(result), 400
|
|
44
|
+
|
|
45
|
+
return jsonify(result), 200
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from flask.views import MethodView
|
|
10
|
+
from flask import render_template, request, Response
|
|
11
|
+
from injector import inject
|
|
12
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LoginSimulationView(MethodView):
|
|
16
|
+
@inject
|
|
17
|
+
def __init__(self,
|
|
18
|
+
profile_service: ProfileService):
|
|
19
|
+
self.profile_service = profile_service
|
|
20
|
+
|
|
21
|
+
def get(self, company_short_name: str = None):
|
|
22
|
+
"""Muestra el formulario para iniciar la simulación."""
|
|
23
|
+
return render_template('login_simulation.html',
|
|
24
|
+
company_short_name=company_short_name
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def post(self, company_short_name: str):
|
|
28
|
+
"""
|
|
29
|
+
Recibe el POST del formulario y actúa como un proxy servidor-a-servidor.
|
|
30
|
+
Llama al endpoint 'external_login' y devuelve su respuesta (HTML y headers).
|
|
31
|
+
"""
|
|
32
|
+
api_key = os.getenv("IATOOLKIT_API_KEY")
|
|
33
|
+
# Obtenemos la URL base de la petición actual para construir la URL interna
|
|
34
|
+
base_url = request.host_url.rstrip('/')
|
|
35
|
+
|
|
36
|
+
# 1. Obtener el user_identifier del formulario
|
|
37
|
+
user_identifier = request.form.get('external_user_id')
|
|
38
|
+
|
|
39
|
+
if not user_identifier:
|
|
40
|
+
return Response("Error: El campo 'external_user_id' es requerido.", status=400)
|
|
41
|
+
|
|
42
|
+
# 2. Preparar la llamada a la API real de external_login
|
|
43
|
+
target_url = f"{base_url}/{company_short_name}/external_login"
|
|
44
|
+
headers = {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
'Authorization': f'Bearer {api_key}'
|
|
47
|
+
}
|
|
48
|
+
# El payload debe ser un diccionario que se convertirá a JSON
|
|
49
|
+
payload = {'user_identifier': user_identifier}
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
# 3. Llamada POST segura desde este servidor al endpoint de IAToolkit
|
|
53
|
+
internal_response = requests.post(
|
|
54
|
+
target_url,
|
|
55
|
+
headers=headers,
|
|
56
|
+
data=json.dumps(payload),
|
|
57
|
+
timeout=120,
|
|
58
|
+
stream=True # Usamos stream para manejar la respuesta eficientemente
|
|
59
|
+
)
|
|
60
|
+
internal_response.raise_for_status()
|
|
61
|
+
|
|
62
|
+
# 4. Creamos una nueva Response de Flask para el navegador del usuario.
|
|
63
|
+
user_response = Response(
|
|
64
|
+
internal_response.iter_content(chunk_size=1024),
|
|
65
|
+
status=internal_response.status_code
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# 5. Copiamos TODAS las cabeceras de la respuesta interna a la respuesta final.
|
|
69
|
+
# Esto es CRUCIAL para que las cookies ('Set-Cookie') lleguen al navegador.
|
|
70
|
+
for key, value in internal_response.headers.items():
|
|
71
|
+
# Excluimos cabeceras que no debemos pasar (controladas por el servidor WSGI)
|
|
72
|
+
if key.lower() not in ['content-encoding', 'content-length', 'transfer-encoding', 'connection']:
|
|
73
|
+
user_response.headers[key] = value
|
|
74
|
+
|
|
75
|
+
return user_response
|
|
76
|
+
|
|
77
|
+
except requests.exceptions.HTTPError as e:
|
|
78
|
+
error_text = f"Error en la llamada interna a la API: {e.response.status_code}. Respuesta: {e.response.text}"
|
|
79
|
+
return Response(error_text, status=e.response.status_code, mimetype='text/plain')
|
|
80
|
+
except requests.exceptions.RequestException as e:
|
|
81
|
+
return Response(f'Error de conexión con el servicio de IA: {str(e)}', status=502, mimetype='text/plain')
|