iatoolkit 0.11.0__tar.gz → 0.12.0__tar.gz

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 (117) hide show
  1. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/PKG-INFO +1 -1
  2. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/pyproject.toml +1 -1
  3. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/routes.py +9 -7
  4. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/iatoolkit.py +0 -4
  5. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/branding_service.py +2 -1
  6. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/profile_service.py +12 -20
  7. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/js/chat_history.js +1 -0
  8. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/js/chat_main.js +76 -51
  9. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +17 -1
  10. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/chat.html +3 -2
  11. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/home.html +3 -3
  12. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/external_chat_login_view.py +34 -17
  13. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/history_view.py +1 -1
  14. iatoolkit-0.12.0/src/iatoolkit/views/login_view.py +124 -0
  15. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit.egg-info/PKG-INFO +1 -1
  16. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit.egg-info/SOURCES.txt +0 -1
  17. iatoolkit-0.11.0/src/iatoolkit/views/chat_view.py +0 -58
  18. iatoolkit-0.11.0/src/iatoolkit/views/login_view.py +0 -60
  19. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/readme.md +0 -0
  20. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/requirements.txt +0 -0
  21. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/setup.cfg +0 -0
  22. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/__init__.py +0 -0
  23. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/base_company.py +0 -0
  24. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/cli_commands.py +0 -0
  25. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/__init__.py +0 -0
  26. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/auth.py +0 -0
  27. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/exceptions.py +0 -0
  28. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/session_manager.py +0 -0
  29. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/common/util.py +0 -0
  30. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/company_registry.py +0 -0
  31. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/__init__.py +0 -0
  32. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/call_service.py +0 -0
  33. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  34. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  35. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  36. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  37. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
  38. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  39. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  40. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/gemini_adapter.py +0 -0
  41. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  42. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/llm_client.py +0 -0
  43. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  44. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/llm_response.py +0 -0
  45. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/mail_app.py +0 -0
  46. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/openai_adapter.py +0 -0
  47. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  48. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/__init__.py +0 -0
  49. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/database_manager.py +0 -0
  50. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/document_repo.py +0 -0
  51. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  52. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/models.py +0 -0
  53. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
  54. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/tasks_repo.py +0 -0
  55. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
  56. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/__init__.py +0 -0
  57. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/benchmark_service.py +0 -0
  58. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
  59. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/document_service.py +0 -0
  60. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/excel_service.py +0 -0
  61. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/file_processor_service.py +0 -0
  62. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/history_service.py +0 -0
  63. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/jwt_service.py +0 -0
  64. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/load_documents_service.py +0 -0
  65. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/mail_service.py +0 -0
  66. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/prompt_manager_service.py +0 -0
  67. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/query_service.py +0 -0
  68. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/search_service.py +0 -0
  69. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/sql_service.py +0 -0
  70. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/tasks_service.py +0 -0
  71. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  72. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  73. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/arrow_up.png +0 -0
  74. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_clinica.png +0 -0
  76. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_maxxa.png +0 -0
  78. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_notaria.png +0 -0
  79. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/logo_umayor.png +0 -0
  81. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/images/upload.png +0 -0
  82. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/js/chat_feedback.js +0 -0
  83. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  84. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/styles/chat_info.css +0 -0
  85. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  86. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  87. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/system_prompts/format_styles.prompt +0 -0
  88. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/system_prompts/query_main.prompt +0 -0
  89. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/system_prompts/sql_rules.prompt +0 -0
  90. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/about.html +0 -0
  91. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/base.html +0 -0
  92. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/change_password.html +0 -0
  93. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  94. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/error.html +0 -0
  95. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  96. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/header.html +0 -0
  97. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/login.html +0 -0
  98. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/signup.html +0 -0
  99. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/templates/test.html +0 -0
  100. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/__init__.py +0 -0
  101. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/change_password_view.py +0 -0
  102. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/chat_token_request_view.py +0 -0
  103. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/download_file_view.py +0 -0
  104. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/external_login_view.py +0 -0
  105. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/file_store_view.py +0 -0
  106. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  107. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/home_view.py +0 -0
  108. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/llmquery_view.py +0 -0
  109. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/prompt_view.py +0 -0
  110. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/signup_view.py +0 -0
  111. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/tasks_review_view.py +0 -0
  112. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/tasks_view.py +0 -0
  113. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/user_feedback_view.py +0 -0
  114. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  115. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  116. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  117. {iatoolkit-0.11.0 → iatoolkit-0.12.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iatoolkit"
7
- version = "0.11.0"
7
+ version = "0.12.0"
8
8
  requires-python = ">=3.11"
9
9
  description = "IAToolkit"
10
10
  readme = "readme.md"
@@ -26,9 +26,8 @@ def register_views(injector, app):
26
26
  from iatoolkit.views.tasks_view import TaskView
27
27
  from iatoolkit.views.tasks_review_view import TaskReviewView
28
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
29
+ from iatoolkit.views.login_view import LoginView, InitiateLoginView
30
+ from iatoolkit.views.external_chat_login_view import InitiateExternalChatView, ExternalChatLoginView
32
31
  from iatoolkit.views.signup_view import SignupView
33
32
  from iatoolkit.views.verify_user_view import VerifyAccountView
34
33
  from iatoolkit.views.forgot_password_view import ForgotPasswordView
@@ -42,16 +41,19 @@ def register_views(injector, app):
42
41
 
43
42
  app.add_url_rule('/', view_func=HomeView.as_view('home'))
44
43
 
45
- # main chat for iatoolkit front
46
- app.add_url_rule('/<company_short_name>/chat', view_func=ChatView.as_view('chat'))
47
-
48
44
  # front if the company internal portal
49
45
  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'))
46
+ app.add_url_rule('/<company_short_name>/external_login/<external_user_id>',
47
+ view_func=ExternalLoginView.as_view('external_login'))
48
+ app.add_url_rule('/<company_short_name>/initiate_external_chat',
49
+ view_func=InitiateExternalChatView.as_view('initiate_chat_login'))
50
+
51
51
  app.add_url_rule('/auth/chat_token', view_func=ChatTokenRequestView.as_view('chat-token'))
52
52
 
53
53
  # main pages for the iatoolkit frontend
54
54
  app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
55
+ app.add_url_rule('/<company_short_name>/initiate_login', view_func=InitiateLoginView.as_view('initiate_login'))
56
+
55
57
  app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
56
58
  app.add_url_rule('/<company_short_name>/logout', 'logout', logout)
57
59
  app.add_url_rule('/logout', 'logout', logout)
@@ -315,12 +315,8 @@ class IAToolkit:
315
315
  """Vincula las vistas después de que el injector ha sido creado"""
316
316
  from iatoolkit.views.llmquery_view import LLMQueryView
317
317
  from iatoolkit.views.home_view import HomeView
318
- from iatoolkit.views.chat_view import ChatView
319
- from iatoolkit.views.change_password_view import ChangePasswordView
320
318
 
321
319
  binder.bind(HomeView, to=HomeView)
322
- binder.bind(ChatView, to=ChatView)
323
- binder.bind(ChangePasswordView, to=ChangePasswordView)
324
320
  binder.bind(LLMQueryView, to=LLMQueryView)
325
321
 
326
322
  logging.info("✅ Views configuradas correctamente")
@@ -104,5 +104,6 @@ class BrandingService:
104
104
  "secondary_text_style": secondary_text_style,
105
105
  "tertiary_text_style": tertiary_text_style,
106
106
  "header_text_color": final_branding_values['header_text_color'],
107
- "css_variables": css_variables
107
+ "css_variables": css_variables,
108
+ "send_button_color": final_branding_values['send_button_color']
108
109
  }
@@ -17,7 +17,6 @@ import secrets
17
17
  import string
18
18
  from datetime import datetime, timezone
19
19
  from iatoolkit.services.user_session_context_service import UserSessionContextService
20
- from iatoolkit.services.query_service import QueryService
21
20
 
22
21
 
23
22
  class ProfileService:
@@ -25,50 +24,43 @@ class ProfileService:
25
24
  def __init__(self,
26
25
  profile_repo: ProfileRepo,
27
26
  session_context_service: UserSessionContextService,
28
- query_service: QueryService,
29
27
  mail_app: MailApp):
30
28
  self.profile_repo = profile_repo
31
29
  self.session_context = session_context_service
32
- self.query_service = query_service
33
30
  self.mail_app = mail_app
34
31
  self.bcrypt = Bcrypt()
35
32
 
36
33
 
37
34
  def login(self, company_short_name: str, email: str, password: str) -> dict:
38
35
  try:
39
- # check if exits
36
+ # check if user exists
40
37
  user = self.profile_repo.get_user_by_email(email)
41
38
  if not user:
42
- return {"error": "Usuario no encontrado"}
39
+ return {'success': False, "message": "Usuario no encontrado"}
43
40
 
44
41
  # check the encrypted password
45
42
  if not check_password_hash(user.password, password):
46
- return {"error": "Contraseña inválida"}
43
+ return {'success': False, "message": "Contraseña inválida"}
47
44
 
48
45
  company = self.get_company_by_short_name(company_short_name)
49
46
  if not company:
50
- return {"error": "Empresa no encontrada"}
47
+ return {'success': False, "message": "Empresa no encontrada"}
51
48
 
52
- # check that user belongs to company
49
+ # check that user belongs to company
53
50
  if company not in user.companies:
54
- return {"error": "Usuario no esta autorizado para esta empresa"}
51
+ return {'success': False, "message": "Usuario no esta autorizado para esta empresa"}
55
52
 
56
53
  if not user.verified:
57
- return {"error": "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."}
54
+ return {'success': False,
55
+ "message": "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."}
58
56
 
59
- # clear history save user data into session manager
57
+ # save user data into session manager
60
58
  self.set_user_session(user=user, company=company)
61
59
 
62
- # initialize company context
63
- self.query_service.llm_init_context(
64
- company_short_name=company_short_name,
65
- local_user_id=user.id
66
- )
67
-
68
- return {"message": "Login exitoso"}
60
+ return {'success': True, "user": user, "message": "Login exitoso"}
69
61
  except Exception as e:
70
- logging.exception(f"login error: {str(e)}")
71
- return {"error": str(e)}
62
+ return {'success': False, "message": str(e)}
63
+
72
64
 
73
65
  def set_user_session(self, user: User, company: Company):
74
66
  SessionManager.set('user_id', user.id)
@@ -92,6 +92,7 @@ $(document).ready(function () {
92
92
 
93
93
  // Copiar el texto al textarea del chat
94
94
  $('#question').val(queryText);
95
+ $('#send-button').removeClass('disabled');
95
96
 
96
97
  // Cerrar el modal
97
98
  $('#historyModal').modal('hide');
@@ -1,6 +1,6 @@
1
1
  // Global variables for request management
2
- let currentAbortController = null;
3
2
  let isRequestInProgress = false;
3
+ let abortController = null;
4
4
 
5
5
  let selectedPrompt = null; // Will hold a lightweight prompt object
6
6
 
@@ -8,6 +8,8 @@ $(document).ready(function () {
8
8
  // --- MAIN EVENT HANDLERS ---
9
9
  $('#send-button').on('click', handleChatMessage);
10
10
  $('#stop-button').on('click', abortCurrentRequest);
11
+ if (window.sendButtonColor)
12
+ $('#send-button i').css('color', window.sendButtonColor);
11
13
 
12
14
  // --- PROMPT ASSISTANT FUNCTIONALITY ---
13
15
  $('.input-area').on('click', '.dropdown-menu a.dropdown-item', function (event) {
@@ -118,75 +120,99 @@ function renderDynamicInputs(fields) {
118
120
  }
119
121
 
120
122
 
123
+
121
124
  /**
122
125
  * Main function to handle sending a chat message.
123
126
  */
124
127
  const handleChatMessage = async function () {
125
- if (isRequestInProgress || $('#send-button').hasClass('disabled')) return;
126
-
127
- const question = $('#question').val().trim();
128
- const promptName = selectedPrompt ? selectedPrompt.prompt : null;
128
+ if (isRequestInProgress || $('#send-button').hasClass('disabled')) {
129
+ return;
130
+ }
129
131
 
130
- let displayMessage = question;
131
- let isEditable = true;
132
- const clientData = {};
132
+ isRequestInProgress = true;
133
+ toggleSendStopButtons(true);
133
134
 
134
- if (selectedPrompt) {
135
- displayMessage = selectedPrompt.description;
136
- isEditable = false; // Prompts are not editable
135
+ try {
136
+ const question = $('#question').val().trim();
137
+ const promptName = selectedPrompt ? selectedPrompt.prompt : null;
137
138
 
138
- (selectedPrompt.custom_fields || []).forEach(field => {
139
- const value = $('#' + field.data_key + '-id').val().trim();
140
- if (value) {
141
- clientData[field.data_key] = value;
142
- }
143
- });
139
+ let displayMessage = question;
140
+ let isEditable = true;
141
+ const clientData = {};
144
142
 
145
- // Append the collected parameter values to the display message
146
- const paramsString = Object.values(clientData).join(', ');
147
- if (paramsString) {
148
- displayMessage += `: ${paramsString}`;
143
+ if (selectedPrompt) {
144
+ displayMessage = selectedPrompt.description;
145
+ isEditable = false;
146
+
147
+ (selectedPrompt.custom_fields || []).forEach(field => {
148
+ const value = $('#' + field.data_key + '-id').val().trim();
149
+ if (value) {
150
+ clientData[field.data_key] = value;
151
+ }
152
+ });
153
+
154
+ const paramsString = Object.values(clientData).join(', ');
155
+ if (paramsString) { displayMessage += `: ${paramsString}`; }
149
156
  }
150
- }
151
157
 
152
- // Si no hay pregunta libre Y no se ha seleccionado un prompt, no hacer nada.
153
- if (!displayMessage) return;
154
-
155
- displayUserMessage(displayMessage, isEditable, question);
156
- showSpinner();
157
- toggleSendStopButtons(true);
158
+ // Simplificado: Si no hay mensaje, el 'finally' se encargará de limpiar.
159
+ // Simplemente salimos de la función.
160
+ if (!displayMessage) {
161
+ return;
162
+ }
158
163
 
159
- resetAllInputs();
164
+ displayUserMessage(displayMessage, isEditable, question);
165
+ showSpinner();
166
+ resetAllInputs();
160
167
 
161
- const files = window.filePond.getFiles();
162
- const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
168
+ const files = window.filePond.getFiles();
169
+ const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
163
170
 
164
- // Prepare data payload
165
- const data = {
166
- question: question,
167
- prompt_name: promptName,
168
- client_data: clientData,
169
- files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
170
- external_user_id: window.externalUserId
171
- };
171
+ const data = {
172
+ question: question,
173
+ prompt_name: promptName,
174
+ client_data: clientData,
175
+ files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
176
+ external_user_id: window.externalUserId
177
+ };
172
178
 
173
- try {
174
179
  const responseData = await callLLMAPI("/llm_query", data, "POST");
175
180
  if (responseData && responseData.answer) {
176
181
  const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
177
182
  displayBotMessage(answerSection);
178
183
  }
179
184
  } catch (error) {
180
- console.error("Error in handleChatMessage:", error);
181
- // Implement error display logic as needed
185
+ if (error.name === 'AbortError') {
186
+ console.log('Petición abortada por el usuario.');
187
+
188
+ // Usando jQuery estándar para construir el elemento ---
189
+ const icon = $('<i>').addClass('bi bi-stop-circle me-2'); // Icono sin "fill" para un look más ligero
190
+ const textSpan = $('<span>').text('La generación de la respuesta ha sido detenida.');
191
+
192
+ const abortMessage = $('<div>')
193
+ .addClass('system-message')
194
+ .append(icon)
195
+ .append(textSpan);
196
+
197
+ displayBotMessage(abortMessage);
198
+ } else {
199
+ console.error("Error in handleChatMessage:", error);
200
+ const errorSection = $('<div>').addClass('error-section').append('<p>Ocurrió un error al procesar la solicitud.</p>');
201
+ displayBotMessage(errorSection);
202
+ }
182
203
  } finally {
204
+ // Este bloque se ejecuta siempre, garantizando que el estado se limpie.
205
+ isRequestInProgress = false;
183
206
  hideSpinner();
184
207
  toggleSendStopButtons(false);
185
208
  updateSendButtonState();
186
- window.filePond.removeFiles();
209
+ if (window.filePond) {
210
+ window.filePond.removeFiles();
211
+ }
187
212
  }
188
213
  };
189
214
 
215
+
190
216
  /**
191
217
  * Resets all inputs to their initial state.
192
218
  */
@@ -274,16 +300,15 @@ const callLLMAPI = async function(apiPath, data, method, timeoutMs = 500000) {
274
300
  headers['X-Chat-Token'] = window.sessionJWT;
275
301
  }
276
302
 
277
- const controller = new AbortController();
278
- currentAbortController = controller;
279
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
303
+ abortController = new AbortController();
304
+ const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
280
305
 
281
306
  try {
282
307
  const response = await fetch(url, {
283
308
  method: method,
284
309
  headers: headers,
285
310
  body: JSON.stringify(data),
286
- signal: controller.signal,
311
+ signal: abortController.signal, // Se usa el signal del controlador global
287
312
  credentials: 'include'
288
313
  });
289
314
  clearTimeout(timeoutId);
@@ -325,7 +350,8 @@ const displayUserMessage = function(message, isEditable, originalQuestion) {
325
350
  const editIcon = $('<i>').addClass('p-2 bi bi-pencil-fill edit-icon').attr('title', 'Edit query').on('click', function () {
326
351
  $('#question').val(originalQuestion).focus();
327
352
  autoResizeTextarea($('#question')[0]);
328
- updateSendButtonState();
353
+
354
+ $('#send-button').removeClass('disabled');
329
355
  });
330
356
  userMessage.append(editIcon);
331
357
  }
@@ -347,9 +373,8 @@ function displayBotMessage(section) {
347
373
  * Aborts the current in-progress API request.
348
374
  */
349
375
  const abortCurrentRequest = function () {
350
- if (currentAbortController && isRequestInProgress) {
351
- window.isManualAbort = true;
352
- currentAbortController.abort();
376
+ if (isRequestInProgress && abortController) {
377
+ abortController.abort();
353
378
  }
354
379
  };
355
380
 
@@ -186,6 +186,20 @@
186
186
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
187
187
  }
188
188
 
189
+ /* Estilo para mensajes sutiles del sistema (ej. abortado) */
190
+ .system-message {
191
+ color: var(--brand-secondary-color, #6c757d); /* Usa el color secundario o un gris por defecto */
192
+ font-style: italic;
193
+ font-size: 0.9rem;
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: start;
197
+ margin: 10px 0;
198
+ padding: 10px;
199
+ opacity: 0.8;
200
+ }
201
+
202
+
189
203
  #prompt-assistant-collapse .card {
190
204
  border-radius: 1.5rem; /* Mismo radio que el chat-input-bar */
191
205
  border: 1px solid #dee2e6; /* Mismo borde que el chat-input-bar */
@@ -233,7 +247,6 @@
233
247
 
234
248
  /* 6. Anulación específica para el botón de ENVIAR usando su ID (Máxima Prioridad) */
235
249
  #send-button i {
236
- color: var(--brand-send-button-color); /* Usa la variable de branding */
237
250
  font-size: 1.7rem; /* Ligeramente más grande */
238
251
  }
239
252
  #send-button:hover i {
@@ -421,4 +434,7 @@
421
434
  color: #ffc107;
422
435
  }
423
436
 
437
+ #send-button i {
438
+ color: var(--brand-send-button-color);
439
+ }
424
440
 
@@ -26,7 +26,7 @@
26
26
  <div class="d-flex align-items-center">
27
27
  <!-- 1. ID de Usuario -->
28
28
  <span style="{{ branding.secondary_text_style }}">
29
- {{ external_user_id or user.email }}
29
+ {{ external_user_id or user_email }}
30
30
  </span>
31
31
 
32
32
  <!-- 2. Separador Vertical -->
@@ -43,7 +43,7 @@
43
43
  </a>
44
44
 
45
45
  <!-- Icono de cerrar sesión (al final) -->
46
- {% if user.email %}
46
+ {% if user_email %}
47
47
  <a href="{{ url_for('logout', company_short_name=company_short_name) }}"
48
48
  class="ms-3 action-icon-style" title="Cerrar sesión" style="color: {{ branding.header_text_color }} !important;">
49
49
  <i class="bi bi-box-arrow-right"></i>
@@ -164,6 +164,7 @@
164
164
  window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
165
165
  window.externalUserId = "{{ external_user_id }}";
166
166
  window.availablePrompts = {{ prompts.message | tojson }};
167
+ window.sendButtonColor = "{{ branding.send_button_color }}";
167
168
 
168
169
  {% if auth_method == 'jwt' and session_jwt %}
169
170
  // Store session JWT if it exists, defined in the same global scope
@@ -13,7 +13,7 @@
13
13
  <div class="border rounded p-4 shadow-sm bg-light">
14
14
  <h4 class="text-muted fw-semibold text-start mb-3">login integrado (IAToolkit)</h4>
15
15
  <form id="login-form"
16
- action="{{ url_for('home', company_short_name=company_short_name) }}"
16
+ action="{{ url_for('initiate_login', company_short_name=company_short_name) }}"
17
17
  method="post">
18
18
  <div class="mb-3">
19
19
  <label for="company_short_name" class="form-label d-block text-muted">Empresa</label>
@@ -112,7 +112,7 @@
112
112
 
113
113
  // Actualizar action del formulario "Iniciar Sesión"
114
114
  if (selectedCompany && selectedCompany.trim() !== '') {
115
- const loginAction = '/' + selectedCompany + '/login';
115
+ const loginAction = '/' + selectedCompany + '/initiate_login';
116
116
  $('#login-form').attr('action', loginAction); // Actualizamos la URL del form
117
117
  } else {
118
118
  $('#login-form').attr('action', '#'); // URL genérica si no hay selección
@@ -161,7 +161,7 @@
161
161
  $button.prop('disabled', true);
162
162
  $spinner.removeClass('d-none');
163
163
 
164
- fetch(`/${selectedCompany}/chat_login`, {
164
+ fetch(`/${selectedCompany}/initiate_external_chat`, {
165
165
  method: 'POST',
166
166
  headers: {
167
167
  'Content-Type': 'application/json',
@@ -5,7 +5,7 @@
5
5
 
6
6
  import os
7
7
  import logging
8
- from flask import request, jsonify, render_template
8
+ from flask import request, jsonify, render_template, url_for, session
9
9
  from flask.views import MethodView
10
10
  from injector import inject
11
11
  from iatoolkit.common.auth import IAuthentication
@@ -15,6 +15,34 @@ from iatoolkit.services.prompt_manager_service import PromptService
15
15
  from iatoolkit.services.jwt_service import JWTService
16
16
  from iatoolkit.services.branding_service import BrandingService
17
17
 
18
+ class InitiateExternalChatView(MethodView):
19
+ @inject
20
+ def __init__(self,
21
+ iauthentication: IAuthentication,
22
+ ):
23
+ self.iauthentication = iauthentication
24
+
25
+ def post(self, company_short_name: str):
26
+ data = request.get_json()
27
+ if not data or 'external_user_id' not in data:
28
+ return jsonify({"error": "Falta external_user_id"}), 400
29
+
30
+ external_user_id = data['external_user_id']
31
+
32
+ # 1. verify access credentials quickly
33
+ iaut = self.iauthentication.verify(
34
+ company_short_name,
35
+ body_external_user_id=external_user_id
36
+ )
37
+ if not iaut.get("success"):
38
+ return jsonify(iaut), 401
39
+
40
+ # 2. Render the shell page, passing the final data URL and user id
41
+ return render_template("login_shell.html",
42
+ data_source_url=url_for('external_chat_login', company_short_name=company_short_name),
43
+ external_user_id=external_user_id
44
+ )
45
+
18
46
  class ExternalChatLoginView(MethodView):
19
47
  @inject
20
48
  def __init__(self,
@@ -39,19 +67,12 @@ class ExternalChatLoginView(MethodView):
39
67
 
40
68
  external_user_id = data['external_user_id']
41
69
 
42
- # 1. get access credentials
43
- iaut = self.iauthentication.verify(
44
- company_short_name,
45
- body_external_user_id=external_user_id
46
- )
47
- if not iaut.get("success"):
48
- return jsonify(iaut), 401
49
-
50
70
  company = self.profile_service.get_company_by_short_name(company_short_name)
51
71
  if not company:
52
72
  return jsonify({"error": "Empresa no encontrada"}), 404
53
73
 
54
74
  try:
75
+
55
76
  # 1. generate a new JWT, our secure access token.
56
77
  token = self.jwt_service.generate_chat_jwt(
57
78
  company_id=company.id,
@@ -75,19 +96,15 @@ class ExternalChatLoginView(MethodView):
75
96
  branding_data = self.branding_service.get_company_branding(company)
76
97
 
77
98
  # 5. render the chat page with the company/user information.
78
- user_agent = request.user_agent
79
- is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
80
-
81
99
  chat_html = render_template("chat.html",
82
100
  company_short_name=company_short_name,
83
- branding=branding_data,
84
- external_user_id=external_user_id,
85
- is_mobile=is_mobile,
86
101
  auth_method='jwt', # login method is JWT
87
102
  session_jwt=token, # pass the token to the front-end
88
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
103
+ external_user_id=external_user_id,
104
+ branding=branding_data,
89
105
  prompts=prompts,
90
- external_login=True)
106
+ iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
107
+ )
91
108
  return chat_html, 200
92
109
 
93
110
  except Exception as e:
@@ -34,7 +34,7 @@ class HistoryView(MethodView):
34
34
  return jsonify(iaut), 401
35
35
 
36
36
  external_user_id = data.get("external_user_id")
37
- local_user_id = data.get("local_user_id", 0)
37
+ local_user_id = iaut.get("local_user_id", 0)
38
38
 
39
39
  try:
40
40
  response = self.history_service.get_history(
@@ -0,0 +1,124 @@
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
+ from injector import inject
9
+ from iatoolkit.repositories.models import User
10
+ from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.services.prompt_manager_service import PromptService
12
+ from iatoolkit.services.branding_service import BrandingService
13
+ from iatoolkit.services.query_service import QueryService
14
+ import os
15
+ from iatoolkit.common.session_manager import SessionManager
16
+
17
+
18
+ class InitiateLoginView(MethodView):
19
+ """
20
+ Handles the initial, fast part of the standard login process.
21
+ Authenticates user credentials, sets up the server-side session,
22
+ and immediately returns the loading shell page.
23
+ """
24
+ @inject
25
+ def __init__(self, profile_service: ProfileService):
26
+ self.profile_service = profile_service
27
+
28
+ def post(self, company_short_name: str):
29
+ # get company info
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="Empresa no encontrada"), 404
34
+
35
+ email = request.form.get('email')
36
+ password = request.form.get('password')
37
+
38
+ # authenticate the user
39
+ response = self.profile_service.login(
40
+ company_short_name=company_short_name,
41
+ email=email,
42
+ password=password
43
+ )
44
+
45
+ if not response['success']:
46
+ return render_template(
47
+ 'login.html',
48
+ company_short_name=company_short_name,
49
+ company=company,
50
+ form_data={
51
+ "email": email,
52
+ "password": password,
53
+ },
54
+ alert_message=response["error"]), 400
55
+
56
+
57
+ # 3. Render the shell page, passing the URL for the heavy lifting
58
+ # The shell's AJAX call will now be authenticated via the session cookie.
59
+ return render_template(
60
+ "login_shell.html",
61
+ data_source_url=url_for('login', company_short_name=company_short_name),
62
+ external_user_id='' # Pass an empty string, the shell will handle it
63
+ )
64
+
65
+
66
+ class LoginView(MethodView):
67
+ @inject
68
+ def __init__(self,
69
+ profile_service: ProfileService,
70
+ query_service: QueryService,
71
+ prompt_service: PromptService,
72
+ branding_service: BrandingService):
73
+ self.profile_service = profile_service
74
+ self.query_service = query_service
75
+ self.prompt_service = prompt_service
76
+ self.branding_service = branding_service
77
+
78
+ def get(self, company_short_name: str):
79
+ # get company info
80
+ company = self.profile_service.get_company_by_short_name(company_short_name)
81
+ if not company:
82
+ return render_template('error.html', message="Empresa no encontrada"), 404
83
+
84
+ return render_template('login.html',
85
+ company=company,
86
+ company_short_name=company_short_name)
87
+
88
+ def post(self, company_short_name: str):
89
+ company = self.profile_service.get_company_by_short_name(company_short_name)
90
+
91
+ # 1. The user is already authenticated by the session cookie set by InitiateLoginView.
92
+ # We just retrieve the user and company IDs from the session.
93
+ user_id = SessionManager.get('user_id')
94
+ user_email = SessionManager.get('user')['email']
95
+
96
+ try:
97
+ # 2. init the company/user LLM context.
98
+ self.query_service.llm_init_context(
99
+ company_short_name=company_short_name,
100
+ local_user_id=user_id
101
+ )
102
+
103
+ # 3. get the prompt list from backend
104
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
105
+
106
+ # 4. get the branding data
107
+ branding_data = self.branding_service.get_company_branding(company)
108
+
109
+ return render_template("chat.html",
110
+ company_short_name=company_short_name,
111
+ auth_method="Session",
112
+ session_jwt=None, # No JWT in this flow
113
+ user_email=user_email,
114
+ branding=branding_data,
115
+ prompts=prompts,
116
+ iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
117
+ ), 200
118
+
119
+ except Exception as e:
120
+ return render_template("error.html",
121
+ company=company,
122
+ company_short_name=company_short_name,
123
+ message="Ha ocurrido un error inesperado."), 500
124
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -96,7 +96,6 @@ src/iatoolkit/templates/test.html
96
96
  src/iatoolkit/views/__init__.py
97
97
  src/iatoolkit/views/change_password_view.py
98
98
  src/iatoolkit/views/chat_token_request_view.py
99
- src/iatoolkit/views/chat_view.py
100
99
  src/iatoolkit/views/download_file_view.py
101
100
  src/iatoolkit/views/external_chat_login_view.py
102
101
  src/iatoolkit/views/external_login_view.py
@@ -1,58 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- from flask import render_template, request, jsonify
7
- from iatoolkit.services.profile_service import ProfileService
8
- from flask.views import MethodView
9
- from injector import inject
10
- import os
11
- from iatoolkit.common.auth import IAuthentication
12
- from iatoolkit.services.prompt_manager_service import PromptService
13
- from iatoolkit.services.branding_service import BrandingService
14
-
15
-
16
- class ChatView(MethodView):
17
- @inject
18
- def __init__(self,
19
- iauthentication: IAuthentication,
20
- prompt_service: PromptService,
21
- profile_service: ProfileService,
22
- branding_service: BrandingService
23
- ):
24
- self.iauthentication = iauthentication
25
- self.profile_service = profile_service
26
- self.prompt_service = prompt_service
27
- self.branding_service = branding_service
28
-
29
- def get(self, company_short_name: str):
30
- # get access credentials
31
- iaut = self.iauthentication.verify(company_short_name)
32
- if not iaut.get("success"):
33
- return jsonify(iaut), 401
34
-
35
- user_agent = request.user_agent
36
- is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
37
- alert_message = request.args.get('alert_message', None)
38
-
39
- # 1. get company info
40
- company = self.profile_service.get_company_by_short_name(company_short_name)
41
- if not company:
42
- return render_template('error.html', message="Empresa no encontrada"), 404
43
-
44
- # 2. get the company prompts
45
- prompts = self.prompt_service.get_user_prompts(company_short_name)
46
-
47
- # 3. get the branding data
48
- branding_data = self.branding_service.get_company_branding(company)
49
-
50
- return render_template("chat.html",
51
- branding=branding_data,
52
- company_short_name=company_short_name,
53
- is_mobile=is_mobile,
54
- alert_message=alert_message,
55
- alert_icon='success' if alert_message else None,
56
- iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL', 'http://localhost:5000'),
57
- prompts=prompts
58
- )
@@ -1,60 +0,0 @@
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
- from injector import inject
9
- from iatoolkit.services.profile_service import ProfileService
10
-
11
- class LoginView(MethodView):
12
- @inject
13
- def __init__(self, profile_service: ProfileService):
14
- self.profile_service = profile_service
15
-
16
- def get(self, company_short_name: str):
17
- # get company info
18
- company = self.profile_service.get_company_by_short_name(company_short_name)
19
- if not company:
20
- return render_template('error.html', message="Empresa no encontrada"), 404
21
-
22
- return render_template('login.html',
23
- company=company,
24
- company_short_name=company_short_name)
25
-
26
- def post(self, company_short_name: str):
27
- # get company info
28
- company = self.profile_service.get_company_by_short_name(company_short_name)
29
- if not company:
30
- return render_template('error.html', message="Empresa no encontrada"), 404
31
-
32
- email = request.form.get('email')
33
- try:
34
- password = request.form.get('password')
35
-
36
- response = self.profile_service.login(
37
- company_short_name=company_short_name,
38
- email=email,
39
- password=password
40
- )
41
-
42
- if "error" in response:
43
- return render_template(
44
- 'login.html',
45
- company_short_name=company_short_name,
46
- company=company,
47
- form_data={
48
- "email": email,
49
- "password": password,
50
- },
51
- alert_message=response["error"]), 400
52
-
53
- return redirect(url_for('chat', company_short_name=company_short_name))
54
-
55
- except Exception as e:
56
- return render_template("error.html",
57
- company=company,
58
- company_short_name=company_short_name,
59
- message="Ha ocurrido un error inesperado."), 500
60
-
File without changes
File without changes
File without changes