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.

Files changed (150) hide show
  1. iatoolkit/__init__.py +27 -35
  2. iatoolkit/base_company.py +3 -35
  3. iatoolkit/cli_commands.py +18 -47
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +48 -0
  6. iatoolkit/common/interfaces/__init__.py +0 -0
  7. iatoolkit/common/interfaces/asset_storage.py +34 -0
  8. iatoolkit/common/interfaces/database_provider.py +39 -0
  9. iatoolkit/common/model_registry.py +159 -0
  10. iatoolkit/common/routes.py +138 -0
  11. iatoolkit/common/session_manager.py +26 -0
  12. iatoolkit/common/util.py +353 -0
  13. iatoolkit/company_registry.py +66 -29
  14. iatoolkit/core.py +514 -0
  15. iatoolkit/infra/__init__.py +5 -0
  16. iatoolkit/infra/brevo_mail_app.py +123 -0
  17. iatoolkit/infra/call_service.py +140 -0
  18. iatoolkit/infra/connectors/__init__.py +5 -0
  19. iatoolkit/infra/connectors/file_connector.py +17 -0
  20. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  21. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  22. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  23. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  24. iatoolkit/infra/connectors/s3_connector.py +33 -0
  25. iatoolkit/infra/google_chat_app.py +57 -0
  26. iatoolkit/infra/llm_providers/__init__.py +0 -0
  27. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  28. iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
  29. iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
  30. iatoolkit/infra/llm_proxy.py +268 -0
  31. iatoolkit/infra/llm_response.py +45 -0
  32. iatoolkit/infra/redis_session_manager.py +122 -0
  33. iatoolkit/locales/en.yaml +222 -0
  34. iatoolkit/locales/es.yaml +225 -0
  35. iatoolkit/repositories/__init__.py +5 -0
  36. iatoolkit/repositories/database_manager.py +187 -0
  37. iatoolkit/repositories/document_repo.py +33 -0
  38. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  39. iatoolkit/repositories/llm_query_repo.py +105 -0
  40. iatoolkit/repositories/models.py +279 -0
  41. iatoolkit/repositories/profile_repo.py +171 -0
  42. iatoolkit/repositories/vs_repo.py +150 -0
  43. iatoolkit/services/__init__.py +5 -0
  44. iatoolkit/services/auth_service.py +193 -0
  45. {services → iatoolkit/services}/benchmark_service.py +7 -7
  46. iatoolkit/services/branding_service.py +153 -0
  47. iatoolkit/services/company_context_service.py +214 -0
  48. iatoolkit/services/configuration_service.py +375 -0
  49. iatoolkit/services/dispatcher_service.py +134 -0
  50. {services → iatoolkit/services}/document_service.py +20 -8
  51. iatoolkit/services/embedding_service.py +148 -0
  52. iatoolkit/services/excel_service.py +156 -0
  53. {services → iatoolkit/services}/file_processor_service.py +36 -21
  54. iatoolkit/services/history_manager_service.py +208 -0
  55. iatoolkit/services/i18n_service.py +104 -0
  56. iatoolkit/services/jwt_service.py +80 -0
  57. iatoolkit/services/language_service.py +89 -0
  58. iatoolkit/services/license_service.py +82 -0
  59. iatoolkit/services/llm_client_service.py +438 -0
  60. iatoolkit/services/load_documents_service.py +174 -0
  61. iatoolkit/services/mail_service.py +213 -0
  62. {services → iatoolkit/services}/profile_service.py +200 -101
  63. iatoolkit/services/prompt_service.py +303 -0
  64. iatoolkit/services/query_service.py +467 -0
  65. iatoolkit/services/search_service.py +55 -0
  66. iatoolkit/services/sql_service.py +169 -0
  67. iatoolkit/services/tool_service.py +246 -0
  68. iatoolkit/services/user_feedback_service.py +117 -0
  69. iatoolkit/services/user_session_context_service.py +213 -0
  70. iatoolkit/static/images/fernando.jpeg +0 -0
  71. iatoolkit/static/images/iatoolkit_core.png +0 -0
  72. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  73. iatoolkit/static/js/chat_feedback_button.js +80 -0
  74. iatoolkit/static/js/chat_filepond.js +85 -0
  75. iatoolkit/static/js/chat_help_content.js +124 -0
  76. iatoolkit/static/js/chat_history_button.js +110 -0
  77. iatoolkit/static/js/chat_logout_button.js +36 -0
  78. iatoolkit/static/js/chat_main.js +401 -0
  79. iatoolkit/static/js/chat_model_selector.js +227 -0
  80. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  81. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  82. iatoolkit/static/js/chat_reload_button.js +38 -0
  83. iatoolkit/static/styles/chat_iatoolkit.css +559 -0
  84. iatoolkit/static/styles/chat_modal.css +133 -0
  85. iatoolkit/static/styles/chat_public.css +135 -0
  86. iatoolkit/static/styles/documents.css +598 -0
  87. iatoolkit/static/styles/landing_page.css +398 -0
  88. iatoolkit/static/styles/llm_output.css +148 -0
  89. iatoolkit/static/styles/onboarding.css +176 -0
  90. iatoolkit/system_prompts/__init__.py +0 -0
  91. iatoolkit/system_prompts/query_main.prompt +30 -23
  92. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  93. iatoolkit/templates/_company_header.html +45 -0
  94. iatoolkit/templates/_login_widget.html +42 -0
  95. iatoolkit/templates/base.html +78 -0
  96. iatoolkit/templates/change_password.html +66 -0
  97. iatoolkit/templates/chat.html +337 -0
  98. iatoolkit/templates/chat_modals.html +185 -0
  99. iatoolkit/templates/error.html +51 -0
  100. iatoolkit/templates/forgot_password.html +51 -0
  101. iatoolkit/templates/onboarding_shell.html +106 -0
  102. iatoolkit/templates/signup.html +79 -0
  103. iatoolkit/views/__init__.py +5 -0
  104. iatoolkit/views/base_login_view.py +96 -0
  105. iatoolkit/views/change_password_view.py +116 -0
  106. iatoolkit/views/chat_view.py +76 -0
  107. iatoolkit/views/embedding_api_view.py +65 -0
  108. iatoolkit/views/forgot_password_view.py +75 -0
  109. iatoolkit/views/help_content_api_view.py +54 -0
  110. iatoolkit/views/history_api_view.py +56 -0
  111. iatoolkit/views/home_view.py +63 -0
  112. iatoolkit/views/init_context_api_view.py +74 -0
  113. iatoolkit/views/llmquery_api_view.py +59 -0
  114. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  115. iatoolkit/views/load_document_api_view.py +65 -0
  116. iatoolkit/views/login_view.py +170 -0
  117. iatoolkit/views/logout_api_view.py +57 -0
  118. iatoolkit/views/profile_api_view.py +46 -0
  119. iatoolkit/views/prompt_api_view.py +37 -0
  120. iatoolkit/views/root_redirect_view.py +22 -0
  121. iatoolkit/views/signup_view.py +100 -0
  122. iatoolkit/views/static_page_view.py +27 -0
  123. iatoolkit/views/user_feedback_api_view.py +60 -0
  124. iatoolkit/views/users_api_view.py +33 -0
  125. iatoolkit/views/verify_user_view.py +60 -0
  126. iatoolkit-0.107.4.dist-info/METADATA +268 -0
  127. iatoolkit-0.107.4.dist-info/RECORD +132 -0
  128. iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
  129. iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  130. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
  131. iatoolkit/iatoolkit.py +0 -413
  132. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  133. iatoolkit-0.3.9.dist-info/METADATA +0 -252
  134. iatoolkit-0.3.9.dist-info/RECORD +0 -32
  135. services/__init__.py +0 -5
  136. services/api_service.py +0 -75
  137. services/dispatcher_service.py +0 -351
  138. services/excel_service.py +0 -98
  139. services/history_service.py +0 -45
  140. services/jwt_service.py +0 -91
  141. services/load_documents_service.py +0 -212
  142. services/mail_service.py +0 -62
  143. services/prompt_manager_service.py +0 -172
  144. services/query_service.py +0 -334
  145. services/search_service.py +0 -32
  146. services/sql_service.py +0 -42
  147. services/tasks_service.py +0 -188
  148. services/user_feedback_service.py +0 -67
  149. services/user_session_context_service.py +0 -85
  150. {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
+