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.

Files changed (159) hide show
  1. iatoolkit/__init__.py +8 -34
  2. iatoolkit/base_company.py +14 -3
  3. iatoolkit/common/routes.py +83 -52
  4. iatoolkit/common/session_manager.py +0 -1
  5. iatoolkit/common/util.py +0 -27
  6. iatoolkit/iatoolkit.py +61 -46
  7. iatoolkit/infra/llm_client.py +7 -8
  8. iatoolkit/infra/openai_adapter.py +1 -1
  9. iatoolkit/infra/redis_session_manager.py +48 -2
  10. iatoolkit/repositories/database_manager.py +17 -2
  11. iatoolkit/repositories/models.py +31 -6
  12. iatoolkit/repositories/profile_repo.py +7 -2
  13. iatoolkit/services/auth_service.py +188 -0
  14. iatoolkit/services/branding_service.py +147 -0
  15. iatoolkit/services/dispatcher_service.py +10 -40
  16. iatoolkit/services/excel_service.py +15 -15
  17. iatoolkit/services/history_service.py +3 -12
  18. iatoolkit/services/jwt_service.py +15 -24
  19. iatoolkit/services/onboarding_service.py +43 -0
  20. iatoolkit/services/profile_service.py +97 -44
  21. iatoolkit/services/query_service.py +124 -81
  22. iatoolkit/services/tasks_service.py +1 -1
  23. iatoolkit/services/user_feedback_service.py +67 -31
  24. iatoolkit/services/user_session_context_service.py +112 -54
  25. iatoolkit/static/images/fernando.jpeg +0 -0
  26. iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
  27. iatoolkit/static/js/chat_history_button.js +126 -0
  28. iatoolkit/static/js/chat_logout_button.js +36 -0
  29. iatoolkit/static/js/chat_main.js +130 -220
  30. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  31. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  32. iatoolkit/static/js/chat_reload_button.js +52 -0
  33. iatoolkit/static/styles/chat_iatoolkit.css +329 -507
  34. iatoolkit/static/styles/chat_modal.css +95 -56
  35. iatoolkit/static/styles/landing_page.css +182 -0
  36. iatoolkit/static/styles/onboarding.css +169 -0
  37. iatoolkit/system_prompts/query_main.prompt +3 -12
  38. iatoolkit/templates/_company_header.html +20 -0
  39. iatoolkit/templates/_login_widget.html +40 -0
  40. iatoolkit/templates/base.html +8 -3
  41. iatoolkit/templates/change_password.html +54 -37
  42. iatoolkit/templates/chat.html +149 -66
  43. iatoolkit/templates/chat_modals.html +47 -18
  44. iatoolkit/templates/error.html +41 -8
  45. iatoolkit/templates/forgot_password.html +37 -24
  46. iatoolkit/templates/index.html +140 -0
  47. iatoolkit/templates/login_simulation.html +34 -0
  48. iatoolkit/templates/onboarding_shell.html +105 -0
  49. iatoolkit/templates/signup.html +64 -66
  50. iatoolkit/views/base_login_view.py +81 -0
  51. iatoolkit/views/change_password_view.py +23 -12
  52. iatoolkit/views/external_login_view.py +61 -28
  53. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  54. iatoolkit/views/forgot_password_view.py +23 -13
  55. iatoolkit/views/history_api_view.py +52 -0
  56. iatoolkit/views/home_view.py +58 -25
  57. iatoolkit/views/index_view.py +14 -0
  58. iatoolkit/views/init_context_api_view.py +68 -0
  59. iatoolkit/views/llmquery_api_view.py +45 -0
  60. iatoolkit/views/login_simulation_view.py +81 -0
  61. iatoolkit/views/login_view.py +118 -34
  62. iatoolkit/views/logout_api_view.py +45 -0
  63. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
  64. iatoolkit/views/signup_view.py +38 -29
  65. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  66. iatoolkit/views/tasks_review_api_view.py +55 -0
  67. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
  68. iatoolkit/views/verify_user_view.py +13 -8
  69. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
  70. iatoolkit-0.63.4.dist-info/RECORD +113 -0
  71. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
  72. iatoolkit/common/auth.py +0 -200
  73. iatoolkit/static/images/arrow_up.png +0 -0
  74. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. iatoolkit/static/images/logo_clinica.png +0 -0
  76. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. iatoolkit/static/images/logo_maxxa.png +0 -0
  78. iatoolkit/static/images/logo_notaria.png +0 -0
  79. iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. iatoolkit/static/images/logo_umayor.png +0 -0
  81. iatoolkit/static/images/upload.png +0 -0
  82. iatoolkit/static/js/chat_history.js +0 -117
  83. iatoolkit/templates/home.html +0 -201
  84. iatoolkit/templates/login.html +0 -43
  85. iatoolkit/views/chat_token_request_view.py +0 -98
  86. iatoolkit/views/chat_view.py +0 -51
  87. iatoolkit/views/download_file_view.py +0 -58
  88. iatoolkit/views/external_chat_login_view.py +0 -88
  89. iatoolkit/views/history_view.py +0 -57
  90. iatoolkit/views/llmquery_view.py +0 -65
  91. iatoolkit/views/tasks_review_view.py +0 -83
  92. iatoolkit-0.8.1.dist-info/RECORD +0 -175
  93. tests/__init__.py +0 -5
  94. tests/common/__init__.py +0 -0
  95. tests/common/test_auth.py +0 -279
  96. tests/common/test_routes.py +0 -42
  97. tests/common/test_session_manager.py +0 -59
  98. tests/common/test_util.py +0 -444
  99. tests/companies/__init__.py +0 -5
  100. tests/conftest.py +0 -36
  101. tests/infra/__init__.py +0 -5
  102. tests/infra/connectors/__init__.py +0 -5
  103. tests/infra/connectors/test_google_drive_connector.py +0 -107
  104. tests/infra/connectors/test_local_file_connector.py +0 -85
  105. tests/infra/connectors/test_s3_connector.py +0 -95
  106. tests/infra/test_call_service.py +0 -92
  107. tests/infra/test_database_manager.py +0 -59
  108. tests/infra/test_gemini_adapter.py +0 -137
  109. tests/infra/test_google_chat_app.py +0 -68
  110. tests/infra/test_llm_client.py +0 -165
  111. tests/infra/test_llm_proxy.py +0 -122
  112. tests/infra/test_mail_app.py +0 -94
  113. tests/infra/test_openai_adapter.py +0 -105
  114. tests/infra/test_redis_session_manager_service.py +0 -117
  115. tests/repositories/__init__.py +0 -5
  116. tests/repositories/test_database_manager.py +0 -87
  117. tests/repositories/test_document_repo.py +0 -76
  118. tests/repositories/test_llm_query_repo.py +0 -340
  119. tests/repositories/test_models.py +0 -38
  120. tests/repositories/test_profile_repo.py +0 -142
  121. tests/repositories/test_tasks_repo.py +0 -76
  122. tests/repositories/test_vs_repo.py +0 -107
  123. tests/services/__init__.py +0 -5
  124. tests/services/test_dispatcher_service.py +0 -274
  125. tests/services/test_document_service.py +0 -181
  126. tests/services/test_excel_service.py +0 -208
  127. tests/services/test_file_processor_service.py +0 -121
  128. tests/services/test_history_service.py +0 -164
  129. tests/services/test_jwt_service.py +0 -255
  130. tests/services/test_load_documents_service.py +0 -112
  131. tests/services/test_mail_service.py +0 -70
  132. tests/services/test_profile_service.py +0 -379
  133. tests/services/test_prompt_manager_service.py +0 -190
  134. tests/services/test_query_service.py +0 -243
  135. tests/services/test_search_service.py +0 -39
  136. tests/services/test_sql_service.py +0 -160
  137. tests/services/test_tasks_service.py +0 -252
  138. tests/services/test_user_feedback_service.py +0 -389
  139. tests/services/test_user_session_context_service.py +0 -132
  140. tests/views/__init__.py +0 -5
  141. tests/views/test_change_password_view.py +0 -191
  142. tests/views/test_chat_token_request_view.py +0 -188
  143. tests/views/test_chat_view.py +0 -98
  144. tests/views/test_download_file_view.py +0 -149
  145. tests/views/test_external_chat_login_view.py +0 -120
  146. tests/views/test_external_login_view.py +0 -102
  147. tests/views/test_file_store_view.py +0 -128
  148. tests/views/test_forgot_password_view.py +0 -142
  149. tests/views/test_history_view.py +0 -336
  150. tests/views/test_home_view.py +0 -61
  151. tests/views/test_llm_query_view.py +0 -154
  152. tests/views/test_login_view.py +0 -114
  153. tests/views/test_prompt_view.py +0 -111
  154. tests/views/test_signup_view.py +0 -140
  155. tests/views/test_tasks_review_view.py +0 -104
  156. tests/views/test_tasks_view.py +0 -130
  157. tests/views/test_user_feedback_view.py +0 -214
  158. tests/views/test_verify_user_view.py +0 -110
  159. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
iatoolkit/__init__.py CHANGED
@@ -4,37 +4,22 @@ IAToolkit Package
4
4
 
5
5
  # Expose main classes and functions at the top level of the package
6
6
 
7
- # Assuming 'toolkit.py' contains the IAToolkit class
8
- from .iatoolkit import IAToolkit, create_app
9
- from .iatoolkit import current_iatoolkit
7
+ # main IAToolkit class
8
+ from .iatoolkit import IAToolkit, current_iatoolkit, create_app
10
9
 
11
- # Assuming 'app_factory.py' contains create_app and register_company
10
+ # for registering the client companies
12
11
  from .company_registry import register_company
13
-
14
- # Assuming 'base_company.py' contains BaseCompany
15
12
  from .base_company import BaseCompany
13
+ from iatoolkit.repositories.database_manager import DatabaseManager
16
14
 
17
15
  # --- Services ---
18
- # Assuming they are in a 'services' sub-package
16
+ from iatoolkit.services.query_service import QueryService
19
17
  from iatoolkit.services.sql_service import SqlService
20
- from iatoolkit.services.excel_service import ExcelService
21
- from iatoolkit.services.dispatcher_service import Dispatcher
22
18
  from iatoolkit.services.document_service import DocumentService
23
19
  from iatoolkit.services.search_service import SearchService
24
20
  from iatoolkit.services.load_documents_service import LoadDocumentsService
25
- from iatoolkit.repositories.profile_repo import ProfileRepo
26
- from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
27
-
28
- from iatoolkit.services.query_service import QueryService
29
- from iatoolkit.services.prompt_manager_service import PromptService
30
- from iatoolkit.repositories.database_manager import DatabaseManager
31
-
21
+ from iatoolkit.services.excel_service import ExcelService
32
22
  from iatoolkit.infra.call_service import CallServiceClient
33
- from iatoolkit.common.util import Utility
34
-
35
-
36
- from iatoolkit.repositories.models import Base, Company, Function, TaskType, Prompt, PromptCategory
37
-
38
23
 
39
24
  __all__ = [
40
25
  'IAToolkit',
@@ -42,23 +27,12 @@ __all__ = [
42
27
  'current_iatoolkit',
43
28
  'register_company',
44
29
  'BaseCompany',
30
+ 'DatabaseManager',
31
+ 'QueryService',
45
32
  'SqlService',
46
33
  'ExcelService',
47
- 'Dispatcher',
48
34
  'DocumentService',
49
35
  'SearchService',
50
- 'QueryService',
51
36
  'LoadDocumentsService',
52
- 'ProfileRepo',
53
- 'LLMQueryRepo',
54
- 'PromptService',
55
- 'DatabaseManager',
56
37
  'CallServiceClient',
57
- 'Utility',
58
- 'Company',
59
- 'Function',
60
- 'TaskType',
61
- 'Base',
62
- 'Prompt',
63
- 'PromptCategory'
64
38
  ]
iatoolkit/base_company.py CHANGED
@@ -26,8 +26,18 @@ class BaseCompany(ABC):
26
26
  self.company = self.profile_repo.get_company_by_short_name(short_name)
27
27
  return self.company
28
28
 
29
- def _create_company(self, name: str, short_name: str) -> Company:
30
- company_obj = Company(name=name, short_name=short_name, allow_jwt=True)
29
+ def _create_company(self,
30
+ short_name: str,
31
+ name: str,
32
+ parameters: dict | None = None,
33
+ branding: dict | None = None,
34
+ onboarding_cards: dict | None = None,
35
+ ) -> Company:
36
+ company_obj = Company(short_name=short_name,
37
+ name=name,
38
+ parameters=parameters,
39
+ branding=branding,
40
+ onboarding_cards=onboarding_cards)
31
41
  self.company = self.profile_repo.create_company(company_obj)
32
42
  return self.company
33
43
 
@@ -67,6 +77,7 @@ class BaseCompany(ABC):
67
77
  **kwargs
68
78
  )
69
79
 
80
+
70
81
  @abstractmethod
71
82
  # initialize all the database tables needed
72
83
  def register_company(self):
@@ -79,7 +90,7 @@ class BaseCompany(ABC):
79
90
 
80
91
  @abstractmethod
81
92
  # get context specific for this company
82
- def get_user_info(self, **kwargs) -> str:
93
+ def get_user_info(self, user_identifier: str) -> dict:
83
94
  raise NotImplementedError("La subclase debe implementar el método get_user_info()")
84
95
 
85
96
  @abstractmethod
@@ -3,80 +3,93 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from flask import render_template, redirect, flash, url_for,send_from_directory, current_app, abort
7
- from iatoolkit.common.session_manager import SessionManager
6
+ from flask import render_template, redirect, url_for,send_from_directory, current_app, abort
8
7
  from flask import jsonify
9
- from iatoolkit.views.history_view import HistoryView
10
- import os
11
-
12
-
13
- def logout(company_short_name: str):
14
- SessionManager.clear()
15
- flash("Has cerrado sesión correctamente", "info")
16
- if company_short_name:
17
- return redirect(url_for('login', company_short_name=company_short_name))
18
- else:
19
- return redirect(url_for('home'))
8
+ from iatoolkit.views.history_api_view import HistoryApiView
20
9
 
21
10
 
22
11
  # this function register all the views
23
12
  def register_views(injector, app):
24
13
 
25
- from iatoolkit.views.llmquery_view import LLMQueryView
26
- from iatoolkit.views.tasks_view import TaskView
27
- from iatoolkit.views.tasks_review_view import TaskReviewView
28
- from iatoolkit.views.home_view import HomeView
29
- from iatoolkit.views.chat_view import ChatView
30
- from iatoolkit.views.login_view import LoginView
31
- from iatoolkit.views.external_chat_login_view import ExternalChatLoginView
14
+ from iatoolkit.views.index_view import IndexView
15
+ from iatoolkit.views.init_context_api_view import InitContextApiView
16
+ from iatoolkit.views.llmquery_api_view import LLMQueryApiView
17
+ from iatoolkit.views.tasks_api_view import TaskApiView
18
+ from iatoolkit.views.tasks_review_api_view import TaskReviewApiView
19
+ from iatoolkit.views.login_simulation_view import LoginSimulationView
32
20
  from iatoolkit.views.signup_view import SignupView
33
21
  from iatoolkit.views.verify_user_view import VerifyAccountView
34
22
  from iatoolkit.views.forgot_password_view import ForgotPasswordView
35
23
  from iatoolkit.views.change_password_view import ChangePasswordView
36
- from iatoolkit.views.file_store_view import FileStoreView
37
- from iatoolkit.views.user_feedback_view import UserFeedbackView
38
- from iatoolkit.views.prompt_view import PromptView
39
- from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
40
- from iatoolkit.views.external_login_view import ExternalLoginView
41
- from iatoolkit.views.download_file_view import DownloadFileView
42
-
43
- app.add_url_rule('/', view_func=HomeView.as_view('home'))
24
+ from iatoolkit.views.file_store_api_view import FileStoreApiView
25
+ from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
26
+ from iatoolkit.views.prompt_api_view import PromptApiView
27
+ from iatoolkit.views.login_view import LoginView, FinalizeContextView
28
+ from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
29
+ from iatoolkit.views.logout_api_view import LogoutApiView
30
+ from iatoolkit.views.home_view import HomeView
44
31
 
45
- # main chat for iatoolkit front
46
- app.add_url_rule('/<company_short_name>/chat', view_func=ChatView.as_view('chat'))
32
+ # iatoolkit home page
33
+ app.add_url_rule('/', view_func=IndexView.as_view('index'))
47
34
 
48
- # front if the company internal portal
49
- app.add_url_rule('/<company_short_name>/chat_login', view_func=ExternalChatLoginView.as_view('external_chat_login'))
50
- app.add_url_rule('/<company_short_name>/external_login/<external_user_id>', view_func=ExternalLoginView.as_view('external_login'))
51
- app.add_url_rule('/auth/chat_token', view_func=ChatTokenRequestView.as_view('chat-token'))
35
+ # company home view
36
+ app.add_url_rule('/<company_short_name>/home', view_func=HomeView.as_view('home'))
52
37
 
53
- # main pages for the iatoolkit frontend
38
+ # login for the iatoolkit integrated frontend
54
39
  app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
40
+
41
+ # this is the login for external users
42
+ app.add_url_rule('/<company_short_name>/external_login',
43
+ view_func=ExternalLoginView.as_view('external_login'))
44
+
45
+ # this endpoint is called when onboarding_shell finish the context load
46
+ app.add_url_rule(
47
+ '/<company_short_name>/finalize',
48
+ view_func=FinalizeContextView.as_view('finalize_no_token')
49
+ )
50
+
51
+ app.add_url_rule(
52
+ '/<company_short_name>/finalize/<token>',
53
+ view_func=FinalizeContextView.as_view('finalize_with_token')
54
+ )
55
+
56
+ # logout
57
+ app.add_url_rule('/<company_short_name>/api/logout',
58
+ view_func=LogoutApiView.as_view('logout'))
59
+
60
+ # this endpoint is called by the JS for changing the token for a session
61
+ app.add_url_rule('/<string:company_short_name>/api/redeem_token',
62
+ view_func = RedeemTokenApiView.as_view('redeem_token'))
63
+
64
+ # init (reset) the company context
65
+ app.add_url_rule('/<company_short_name>/api/init-context',
66
+ view_func=InitContextApiView.as_view('init-context'),
67
+ methods=['POST', 'OPTIONS'])
68
+
69
+ # register new user, account verification and forgot password
55
70
  app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
56
- app.add_url_rule('/<company_short_name>/logout', 'logout', logout)
57
- app.add_url_rule('/logout', 'logout', logout)
58
71
  app.add_url_rule('/<company_short_name>/verify/<token>', view_func=VerifyAccountView.as_view('verify_account'))
59
72
  app.add_url_rule('/<company_short_name>/forgot-password', view_func=ForgotPasswordView.as_view('forgot_password'))
60
73
  app.add_url_rule('/<company_short_name>/change-password/<token>', view_func=ChangePasswordView.as_view('change_password'))
61
74
 
62
- # this are backend endpoints mainly
63
- app.add_url_rule('/<company_short_name>/llm_query', view_func=LLMQueryView.as_view('llm_query'))
64
- app.add_url_rule('/<company_short_name>/feedback', view_func=UserFeedbackView.as_view('feedback'))
65
- app.add_url_rule('/<company_short_name>/prompts', view_func=PromptView.as_view('prompt'))
66
- app.add_url_rule('/<company_short_name>/history', view_func=HistoryView.as_view('history'))
67
- app.add_url_rule('/tasks', view_func=TaskView.as_view('tasks'))
68
- app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewView.as_view('tasks-review'))
69
- app.add_url_rule('/load', view_func=FileStoreView.as_view('load'))
75
+ # main chat query, used by the JS in the browser (with credentials)
76
+ # can be used also for executing iatoolkit prompts
77
+ app.add_url_rule('/<company_short_name>/api/llm_query', view_func=LLMQueryApiView.as_view('llm_query_api'))
70
78
 
71
- app.add_url_rule(
72
- '/about', # URL de la ruta
73
- view_func=lambda: render_template('about.html'))
79
+ # open the promt directory
80
+ app.add_url_rule('/<company_short_name>/api/prompts', view_func=PromptApiView.as_view('prompt'))
74
81
 
75
- app.add_url_rule('/version', 'version',
76
- lambda: jsonify({"version": app.config['VERSION']}))
82
+ # feedback and history
83
+ app.add_url_rule('/<company_short_name>/api/feedback', view_func=UserFeedbackApiView.as_view('feedback'))
84
+ app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
85
+
86
+ # tasks management endpoints: create task, and review answer
87
+ app.add_url_rule('/tasks', view_func=TaskApiView.as_view('tasks'))
88
+ app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewApiView.as_view('tasks-review'))
89
+
90
+ # this endpoint is for upload documents into the vector store (api-key)
91
+ app.add_url_rule('/api/load', view_func=FileStoreApiView.as_view('load_api'))
77
92
 
78
- app.add_url_rule('/<company_short_name>/<external_user_id>/download-file/<path:filename>',
79
- view_func=DownloadFileView.as_view('download-file'))
80
93
 
81
94
  @app.route('/download/<path:filename>')
82
95
  def download_file(filename):
@@ -99,3 +112,21 @@ def register_views(injector, app):
99
112
  except FileNotFoundError:
100
113
  abort(404)
101
114
 
115
+ # login testing
116
+ app.add_url_rule('/<company_short_name>/login_test',
117
+ view_func=LoginSimulationView.as_view('login_test'))
118
+
119
+ app.add_url_rule(
120
+ '/about', # URL de la ruta
121
+ view_func=lambda: render_template('about.html'))
122
+
123
+ app.add_url_rule('/version', 'version',
124
+ lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
125
+
126
+
127
+ # hacer que la raíz '/' vaya al home de iatoolkit
128
+ @app.route('/')
129
+ def root_redirect():
130
+ return redirect(url_for('index'))
131
+
132
+
@@ -5,7 +5,6 @@
5
5
 
6
6
  from flask import session
7
7
 
8
-
9
8
  class SessionManager:
10
9
  @staticmethod
11
10
  def set(key, value):
iatoolkit/common/util.py CHANGED
@@ -9,7 +9,6 @@ from iatoolkit.common.exceptions import IAToolkitException
9
9
  from injector import inject
10
10
  import os
11
11
  from jinja2 import Environment, FileSystemLoader
12
- from iatoolkit.common.session_manager import SessionManager
13
12
  from datetime import datetime, date
14
13
  from decimal import Decimal
15
14
  import yaml
@@ -22,30 +21,8 @@ class Utility:
22
21
  def __init__(self):
23
22
  self.encryption_key = os.getenv('FERNET_KEY')
24
23
 
25
- @staticmethod
26
- def resolve_user_identifier(external_user_id: str = None, local_user_id: int = 0) -> tuple[str, bool]:
27
- """
28
- Resuelve un identificador único de usuario desde external_user_id o local_user_id.
29
-
30
- Lógica:
31
- - Si external_user_id existe y no está vacío: usar external_user_id
32
- - Si no, y local_user_id > 0: obtener email de la sesión actual y retornarlo como ID
33
- - Si ninguno: retornar string vacío
34
-
35
- """
36
- if external_user_id and external_user_id.strip():
37
- return external_user_id.strip(), False
38
- elif local_user_id and local_user_id > 0:
39
- # get the user information from the session
40
- user_data = SessionManager.get('user')
41
- if user_data:
42
- return user_data.get('email', ''), True
43
-
44
- return "", False
45
-
46
24
  def render_prompt_from_template(self,
47
25
  template_pathname: str,
48
- query: str = None,
49
26
  client_data: dict = {},
50
27
  **kwargs) -> str:
51
28
 
@@ -58,8 +35,6 @@ class Utility:
58
35
  env = Environment(loader=FileSystemLoader(template_dir))
59
36
  template = env.get_template(template_file)
60
37
 
61
- kwargs["query"] = query
62
-
63
38
  # add all the keys in client_data to kwargs
64
39
  kwargs.update(client_data)
65
40
 
@@ -74,7 +49,6 @@ class Utility:
74
49
  def render_prompt_from_string(self,
75
50
  template_string: str,
76
51
  searchpath: str | list[str] = None,
77
- query: str = None,
78
52
  client_data: dict = {},
79
53
  **kwargs) -> str:
80
54
  """
@@ -97,7 +71,6 @@ class Utility:
97
71
  env = Environment(loader=loader)
98
72
  template = env.from_string(template_string)
99
73
 
100
- kwargs["query"] = query
101
74
  kwargs.update(client_data)
102
75
 
103
76
  prompt = template.render(**kwargs)
iatoolkit/iatoolkit.py CHANGED
@@ -15,10 +15,11 @@ import logging
15
15
  import os
16
16
  from typing import Optional, Dict, Any
17
17
  from iatoolkit.repositories.database_manager import DatabaseManager
18
-
19
- from injector import Binder, singleton, Injector
18
+ from werkzeug.middleware.proxy_fix import ProxyFix
19
+ from injector import Binder, Injector, singleton
20
20
  from importlib.metadata import version as _pkg_version, PackageNotFoundError
21
21
 
22
+ IATOOLKIT_VERSION = "0.63.4"
22
23
 
23
24
  # global variable for the unique instance of IAToolkit
24
25
  _iatoolkit_instance: Optional['IAToolkit'] = None
@@ -51,7 +52,7 @@ class IAToolkit:
51
52
  self.app = None
52
53
  self.db_manager = None
53
54
  self._injector = None
54
- self.version = "0.0.0+dev"
55
+ self.version = IATOOLKIT_VERSION # default version
55
56
 
56
57
  @classmethod
57
58
  def get_instance(cls) -> 'IAToolkit':
@@ -87,7 +88,7 @@ class IAToolkit:
87
88
  # and other integrations, as views are handled manually.
88
89
  FlaskInjector(app=self.app, injector=self._injector)
89
90
 
90
- # Step 6: initialize dispatcher and registered compaies
91
+ # Step 6: initialize dispatcher and registered companies
91
92
  self._init_dispatcher_and_company_instances()
92
93
 
93
94
  # Step 7: Finalize setup within the application context
@@ -100,11 +101,6 @@ class IAToolkit:
100
101
  # Step 8: define the download_dir for excel's
101
102
  self._setup_download_dir()
102
103
 
103
- try:
104
- self.version = _pkg_version("iatoolkit")
105
- except PackageNotFoundError:
106
- pass
107
-
108
104
  logging.info(f"🎉 IAToolkit v{self.version} inicializado correctamente")
109
105
  self._initialized = True
110
106
  return self.app
@@ -114,8 +110,9 @@ class IAToolkit:
114
110
  return self.config.get(key, os.getenv(key, default))
115
111
 
116
112
  def _setup_logging(self):
117
- log_level_str = self._get_config_value('FLASK_ENV', 'production')
118
- log_level = logging.INFO if log_level_str in ('dev', 'development') else logging.WARNING
113
+ # Lee el nivel de log desde una variable de entorno, con 'INFO' como valor por defecto.
114
+ log_level_name = os.getenv('LOG_LEVEL', 'INFO').upper()
115
+ log_level = getattr(logging, log_level_name, logging.INFO)
119
116
 
120
117
  logging.basicConfig(
121
118
  level=log_level,
@@ -144,11 +141,21 @@ class IAToolkit:
144
141
  is_https = self._get_config_value('USE_HTTPS', 'false').lower() == 'true'
145
142
  is_dev = self._get_config_value('FLASK_ENV') == 'development'
146
143
 
144
+ # get the iatoolkit domain
145
+ parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
146
+ domain = parsed_url.netloc
147
+
148
+ try:
149
+ self.version = _pkg_version("iatoolkit")
150
+ except PackageNotFoundError:
151
+ pass
152
+
153
+
147
154
  self.app.config.update({
148
155
  'VERSION': self.version,
149
156
  'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
150
- 'SESSION_COOKIE_SAMESITE': "None" if is_https else "Lax",
151
- 'SESSION_COOKIE_SECURE': is_https,
157
+ 'SESSION_COOKIE_SAMESITE': "None",
158
+ 'SESSION_COOKIE_SECURE': True,
152
159
  'SESSION_PERMANENT': False,
153
160
  'SESSION_USE_SIGNER': True,
154
161
  'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
@@ -156,6 +163,12 @@ class IAToolkit:
156
163
  'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
157
164
  })
158
165
 
166
+ if parsed_url.scheme == 'https':
167
+ self.app.config['PREFERRED_URL_SCHEME'] = 'https'
168
+
169
+ # 2. ProxyFix para no tener problemas con iframes y rutas
170
+ self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
171
+
159
172
  # Configuración para tokenizers en desarrollo
160
173
  if is_dev:
161
174
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
@@ -172,6 +185,15 @@ class IAToolkit:
172
185
  self.db_manager.create_all()
173
186
  logging.info("✅ Base de datos configurada correctamente")
174
187
 
188
+ @self.app.teardown_appcontext
189
+ def remove_session(exception=None):
190
+ """
191
+ Flask calls this after each request.
192
+ It ensures the SQLAlchemy session is properly closed
193
+ and the DB connection is returned to the pool.
194
+ """
195
+ self.db_manager.scoped_session.remove()
196
+
175
197
  def _setup_redis_sessions(self):
176
198
  redis_url = self._get_config_value('REDIS_URL')
177
199
  if not redis_url:
@@ -202,19 +224,19 @@ class IAToolkit:
202
224
 
203
225
  def _setup_cors(self):
204
226
  """🌐 Configura CORS"""
205
- # Origins por defecto para desarrollo
227
+ from iatoolkit.company_registry import get_company_registry
228
+
229
+ # default CORS origin
206
230
  default_origins = [
207
- "http://localhost:5001",
208
- "http://127.0.0.1:5001",
209
231
  os.getenv('IATOOLKIT_BASE_URL')
210
232
  ]
211
233
 
212
- # Obtener origins adicionales desde configuración/env
234
+ # Iterate through the registered company names
213
235
  extra_origins = []
214
- for i in range(1, 11): # Soporte para CORS_ORIGIN_1 a CORS_ORIGIN_10
215
- origin = self._get_config_value(f'CORS_ORIGIN_{i}')
216
- if origin:
217
- extra_origins.append(origin)
236
+ all_company_instances = get_company_registry().get_all_company_instances()
237
+ for company_name, company_instance in all_company_instances.items():
238
+ cors_origin = company_instance.company.parameters.get('cors_origin', [])
239
+ extra_origins += cors_origin
218
240
 
219
241
  all_origins = default_origins + extra_origins
220
242
 
@@ -229,7 +251,6 @@ class IAToolkit:
229
251
 
230
252
  logging.info(f"✅ CORS configurado para: {all_origins}")
231
253
 
232
-
233
254
  def _configure_core_dependencies(self, binder: Binder):
234
255
  """⚙️ Configures all system dependencies."""
235
256
  try:
@@ -241,7 +262,6 @@ class IAToolkit:
241
262
  self._bind_repositories(binder)
242
263
  self._bind_services(binder)
243
264
  self._bind_infrastructure(binder)
244
- self._bind_views(binder)
245
265
 
246
266
  logging.info("✅ Dependencias configuradas correctamente")
247
267
 
@@ -278,6 +298,7 @@ class IAToolkit:
278
298
  from iatoolkit.services.profile_service import ProfileService
279
299
  from iatoolkit.services.jwt_service import JWTService
280
300
  from iatoolkit.services.dispatcher_service import Dispatcher
301
+ from iatoolkit.services.branding_service import BrandingService
281
302
 
282
303
  binder.bind(QueryService, to=QueryService)
283
304
  binder.bind(TaskService, to=TaskService)
@@ -290,38 +311,23 @@ class IAToolkit:
290
311
  binder.bind(ProfileService, to=ProfileService)
291
312
  binder.bind(JWTService, to=JWTService)
292
313
  binder.bind(Dispatcher, to=Dispatcher)
314
+ binder.bind(BrandingService, to=BrandingService)
293
315
 
294
316
  def _bind_infrastructure(self, binder: Binder):
295
317
  from iatoolkit.infra.llm_client import llmClient
296
318
  from iatoolkit.infra.llm_proxy import LLMProxy
297
319
  from iatoolkit.infra.google_chat_app import GoogleChatApp
298
320
  from iatoolkit.infra.mail_app import MailApp
299
- from iatoolkit.common.auth import IAuthentication
321
+ from iatoolkit.services.auth_service import AuthService
300
322
  from iatoolkit.common.util import Utility
301
323
 
302
-
303
-
304
- binder.bind(LLMProxy, to=LLMProxy, scope=singleton)
305
- binder.bind(llmClient, to=llmClient, scope=singleton)
324
+ binder.bind(LLMProxy, to=LLMProxy)
325
+ binder.bind(llmClient, to=llmClient)
306
326
  binder.bind(GoogleChatApp, to=GoogleChatApp)
307
327
  binder.bind(MailApp, to=MailApp)
308
- binder.bind(IAuthentication, to=IAuthentication)
328
+ binder.bind(AuthService, to=AuthService)
309
329
  binder.bind(Utility, to=Utility)
310
330
 
311
- def _bind_views(self, binder: Binder):
312
- """Vincula las vistas después de que el injector ha sido creado"""
313
- from iatoolkit.views.llmquery_view import LLMQueryView
314
- from iatoolkit.views.home_view import HomeView
315
- from iatoolkit.views.chat_view import ChatView
316
- from iatoolkit.views.change_password_view import ChangePasswordView
317
-
318
- binder.bind(HomeView, to=HomeView)
319
- binder.bind(ChatView, to=ChatView)
320
- binder.bind(ChangePasswordView, to=ChangePasswordView)
321
- binder.bind(LLMQueryView, to=LLMQueryView)
322
-
323
- logging.info("✅ Views configuradas correctamente")
324
-
325
331
  def _setup_additional_services(self):
326
332
  Bcrypt(self.app)
327
333
 
@@ -359,14 +365,23 @@ class IAToolkit:
359
365
  @self.app.context_processor
360
366
  def inject_globals():
361
367
  from iatoolkit.common.session_manager import SessionManager
368
+ from iatoolkit.services.profile_service import ProfileService
369
+
370
+ profile_service = self._injector.get(ProfileService)
371
+ user_profile = profile_service.get_current_session_info().get('profile', {})
372
+
362
373
  return {
363
374
  'url_for': url_for,
364
375
  'iatoolkit_version': self.version,
365
376
  'app_name': 'IAToolkit',
366
- 'user': SessionManager.get('user'),
367
- 'user_company': SessionManager.get('company_short_name'),
377
+ 'user_identifier': SessionManager.get('user_identifier'),
378
+ 'company_short_name': SessionManager.get('company_short_name'),
379
+ 'user_is_local': user_profile.get('user_is_local'),
380
+ 'user_email': user_profile.get('user_email'),
381
+ 'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
368
382
  }
369
383
 
384
+
370
385
  def _get_default_static_folder(self) -> str:
371
386
  try:
372
387
  current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src/iatoolkit
@@ -409,7 +424,7 @@ class IAToolkit:
409
424
 
410
425
  def _setup_download_dir(self):
411
426
  # 1. set the default download directory
412
- default_download_dir = os.path.join(os.getcwd(), 'downloads')
427
+ default_download_dir = os.path.join(os.getcwd(), 'iatoolkit-downloads')
413
428
 
414
429
  # 3. if user specified one, use it
415
430
  download_dir = self.app.config.get('IATOOLKIT_DOWNLOAD_DIR', default_download_dir)
@@ -21,6 +21,7 @@ import tiktoken
21
21
  from typing import Dict, Optional, List
22
22
  from iatoolkit.services.dispatcher_service import Dispatcher
23
23
 
24
+ CONTEXT_ERROR_MESSAGE = 'Tu consulta supera el límite de contexto, utiliza el boton de recarga de contexto.'
24
25
 
25
26
  class llmClient:
26
27
  _llm_clients_cache = {} # class attribute, for the clients cache
@@ -116,7 +117,7 @@ class llmClient:
116
117
 
117
118
  # in case of context error
118
119
  if "context_length_exceeded" in str(e):
119
- error_message = 'Tu consulta supera el limite de contexto, sale e ingresa de nuevo a IAToolkit'
120
+ error_message = CONTEXT_ERROR_MESSAGE
120
121
 
121
122
  raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
122
123
 
@@ -256,25 +257,23 @@ class llmClient:
256
257
 
257
258
  # in case of context error
258
259
  if "context_length_exceeded" in str(e):
259
- error_message = 'Tu consulta supera el limite de contexto, sale e ingresa de nuevo a IAToolkit'
260
+ error_message = CONTEXT_ERROR_MESSAGE
260
261
  elif "string_above_max_length" in str(e):
261
- error_message = 'La respuesta es muy larga, trata de filtrar/restringuir tu consulta'
262
+ error_message = 'La respuesta es muy extensa, trata de filtrar/restringuir tu consulta'
262
263
 
263
264
  raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
264
265
 
265
266
  def set_company_context(self,
266
267
  company: Company,
267
268
  company_base_context: str,
268
- model: str = None) -> str:
269
+ model) -> str:
269
270
 
270
- if model:
271
- self.model = model
272
- logging.info(f"initializing model '{self.model}' with company context: {self.count_tokens(company_base_context)} tokens...")
271
+ logging.info(f"initializing model '{model}' with company context: {self.count_tokens(company_base_context)} tokens...")
273
272
 
274
273
  llm_proxy = self.llm_proxy_factory.create_for_company(company)
275
274
  try:
276
275
  response = llm_proxy.create_response(
277
- model=self.model,
276
+ model=model,
278
277
  input=[{
279
278
  "role": "system",
280
279
  "content": company_base_context
@@ -55,7 +55,7 @@ class OpenAIAdapter:
55
55
 
56
56
  # En caso de error de contexto
57
57
  if "context_length_exceeded" in str(e):
58
- error_message = 'Tu consulta supera el limite de contexto, sale e ingresa de nuevo a IAToolkit'
58
+ error_message = 'Tu consulta supera el limite de contexto. Reinicia el contexto con el boton de la barra superior.'
59
59
 
60
60
  raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
61
61