iatoolkit 0.71.4__py3-none-any.whl → 1.4.2__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.
Files changed (114) hide show
  1. iatoolkit/__init__.py +19 -7
  2. iatoolkit/base_company.py +1 -71
  3. iatoolkit/cli_commands.py +9 -21
  4. iatoolkit/common/exceptions.py +2 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +38 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +53 -32
  10. iatoolkit/common/util.py +17 -12
  11. iatoolkit/company_registry.py +55 -14
  12. iatoolkit/{iatoolkit.py → core.py} +102 -72
  13. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  14. iatoolkit/infra/llm_providers/__init__.py +0 -0
  15. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  16. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  17. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  18. iatoolkit/infra/llm_proxy.py +235 -134
  19. iatoolkit/infra/llm_response.py +5 -0
  20. iatoolkit/locales/en.yaml +134 -4
  21. iatoolkit/locales/es.yaml +293 -162
  22. iatoolkit/repositories/database_manager.py +92 -22
  23. iatoolkit/repositories/document_repo.py +7 -0
  24. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  25. iatoolkit/repositories/llm_query_repo.py +36 -22
  26. iatoolkit/repositories/models.py +86 -95
  27. iatoolkit/repositories/profile_repo.py +64 -13
  28. iatoolkit/repositories/vs_repo.py +31 -28
  29. iatoolkit/services/auth_service.py +1 -1
  30. iatoolkit/services/branding_service.py +1 -1
  31. iatoolkit/services/company_context_service.py +96 -39
  32. iatoolkit/services/configuration_service.py +329 -67
  33. iatoolkit/services/dispatcher_service.py +51 -227
  34. iatoolkit/services/document_service.py +10 -1
  35. iatoolkit/services/embedding_service.py +9 -6
  36. iatoolkit/services/excel_service.py +50 -2
  37. iatoolkit/services/file_processor_service.py +0 -5
  38. iatoolkit/services/history_manager_service.py +208 -0
  39. iatoolkit/services/jwt_service.py +1 -1
  40. iatoolkit/services/knowledge_base_service.py +412 -0
  41. iatoolkit/services/language_service.py +8 -2
  42. iatoolkit/services/license_service.py +82 -0
  43. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
  44. iatoolkit/services/load_documents_service.py +18 -47
  45. iatoolkit/services/mail_service.py +171 -25
  46. iatoolkit/services/profile_service.py +69 -36
  47. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
  48. iatoolkit/services/query_service.py +229 -203
  49. iatoolkit/services/sql_service.py +116 -34
  50. iatoolkit/services/tool_service.py +246 -0
  51. iatoolkit/services/user_feedback_service.py +18 -6
  52. iatoolkit/services/user_session_context_service.py +121 -51
  53. iatoolkit/static/images/iatoolkit_core.png +0 -0
  54. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  55. iatoolkit/static/js/chat_feedback_button.js +1 -1
  56. iatoolkit/static/js/chat_help_content.js +4 -4
  57. iatoolkit/static/js/chat_main.js +61 -9
  58. iatoolkit/static/js/chat_model_selector.js +227 -0
  59. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  60. iatoolkit/static/js/chat_reload_button.js +4 -1
  61. iatoolkit/static/styles/chat_iatoolkit.css +59 -3
  62. iatoolkit/static/styles/chat_public.css +28 -0
  63. iatoolkit/static/styles/documents.css +598 -0
  64. iatoolkit/static/styles/landing_page.css +223 -7
  65. iatoolkit/static/styles/llm_output.css +34 -1
  66. iatoolkit/system_prompts/__init__.py +0 -0
  67. iatoolkit/system_prompts/query_main.prompt +28 -3
  68. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  69. iatoolkit/templates/_company_header.html +30 -5
  70. iatoolkit/templates/_login_widget.html +3 -3
  71. iatoolkit/templates/base.html +13 -0
  72. iatoolkit/templates/chat.html +45 -3
  73. iatoolkit/templates/forgot_password.html +3 -2
  74. iatoolkit/templates/onboarding_shell.html +1 -2
  75. iatoolkit/templates/signup.html +3 -0
  76. iatoolkit/views/base_login_view.py +8 -3
  77. iatoolkit/views/change_password_view.py +1 -1
  78. iatoolkit/views/chat_view.py +76 -0
  79. iatoolkit/views/forgot_password_view.py +9 -4
  80. iatoolkit/views/history_api_view.py +3 -3
  81. iatoolkit/views/home_view.py +4 -2
  82. iatoolkit/views/init_context_api_view.py +1 -1
  83. iatoolkit/views/llmquery_api_view.py +4 -3
  84. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  85. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
  86. iatoolkit/views/login_view.py +25 -8
  87. iatoolkit/views/logout_api_view.py +10 -2
  88. iatoolkit/views/prompt_api_view.py +1 -1
  89. iatoolkit/views/rag_api_view.py +216 -0
  90. iatoolkit/views/root_redirect_view.py +22 -0
  91. iatoolkit/views/signup_view.py +12 -4
  92. iatoolkit/views/static_page_view.py +27 -0
  93. iatoolkit/views/users_api_view.py +33 -0
  94. iatoolkit/views/verify_user_view.py +1 -1
  95. iatoolkit-1.4.2.dist-info/METADATA +268 -0
  96. iatoolkit-1.4.2.dist-info/RECORD +133 -0
  97. iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  98. iatoolkit/repositories/tasks_repo.py +0 -52
  99. iatoolkit/services/history_service.py +0 -37
  100. iatoolkit/services/search_service.py +0 -55
  101. iatoolkit/services/tasks_service.py +0 -188
  102. iatoolkit/templates/about.html +0 -13
  103. iatoolkit/templates/index.html +0 -145
  104. iatoolkit/templates/login_simulation.html +0 -45
  105. iatoolkit/views/external_login_view.py +0 -73
  106. iatoolkit/views/index_view.py +0 -14
  107. iatoolkit/views/login_simulation_view.py +0 -93
  108. iatoolkit/views/tasks_api_view.py +0 -72
  109. iatoolkit/views/tasks_review_api_view.py +0 -55
  110. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  111. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  112. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
  113. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
  114. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
@@ -38,7 +38,6 @@
38
38
  <div class="ob-stack">
39
39
  <h1 id="ob-brand-header" class="ob-brand-header">
40
40
  <span class="brand-name text-brand-primary">{{ branding.name | default('IAToolkit') }}</span>
41
- <span class="brand-rest"> IA</span>
42
41
  </h1>
43
42
 
44
43
  <div id="card-container" class="ob-card">
@@ -60,7 +59,7 @@
60
59
 
61
60
  <div id="loading-status" class="ob-loading-band">
62
61
  <div class="spinner"></div>
63
- <p>Inicializando el contexto de {{ branding.name }} para la IA...</p>
62
+ <p>{{ t('ui.chat.init_context') }} </p>
64
63
  </div>
65
64
  </div>
66
65
  </div>
@@ -59,6 +59,9 @@
59
59
  <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
60
60
  </div>
61
61
 
62
+ <!-- language: html -->
63
+ <input type="hidden" name="lang" value="{{ lang or 'en' }}">
64
+
62
65
  <!-- Botón actualizado con la clase de branding -->
63
66
  <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.signup.signup_button') }}</button>
64
67
  </form>
@@ -12,7 +12,7 @@ from iatoolkit.services.auth_service import AuthService
12
12
  from iatoolkit.services.query_service import QueryService
13
13
  from iatoolkit.services.branding_service import BrandingService
14
14
  from iatoolkit.services.configuration_service import ConfigurationService
15
- from iatoolkit.services.prompt_manager_service import PromptService
15
+ from iatoolkit.services.prompt_service import PromptService
16
16
  from iatoolkit.services.i18n_service import I18nService
17
17
  from iatoolkit.common.util import Utility
18
18
  from iatoolkit.services.jwt_service import JWTService
@@ -74,6 +74,9 @@ class BaseLoginView(MethodView):
74
74
  )
75
75
  else:
76
76
  # --- FAST PATH: Render the chat page directly ---
77
+ # LLM configuration: default model and availables
78
+ default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
79
+
77
80
  prompts = self.prompt_service.get_user_prompts(company_short_name)
78
81
 
79
82
  # Get the entire 'js_messages' block in the correct language.
@@ -87,5 +90,7 @@ class BaseLoginView(MethodView):
87
90
  branding=branding_data,
88
91
  onboarding_cards=onboarding_cards,
89
92
  js_translations=js_translations,
90
- redeem_token=redeem_token
91
- )
93
+ redeem_token=redeem_token,
94
+ llm_default_model=default_llm_model,
95
+ llm_available_models = available_llm_models,
96
+ )
@@ -24,7 +24,7 @@ class ChangePasswordView(MethodView):
24
24
  self.branding_service = branding_service
25
25
  self.i18n_service = i18n_service
26
26
 
27
- self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
27
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
28
28
  self.bcrypt = Bcrypt()
29
29
 
30
30
  def get(self, company_short_name: str, token: str):
@@ -0,0 +1,76 @@
1
+ from flask import render_template, redirect, url_for, request
2
+ from flask.views import MethodView
3
+ from injector import inject
4
+ from iatoolkit.services.profile_service import ProfileService
5
+ from iatoolkit.services.branding_service import BrandingService
6
+ from iatoolkit.services.configuration_service import ConfigurationService
7
+ from iatoolkit.services.prompt_service import PromptService
8
+ from iatoolkit.services.i18n_service import I18nService
9
+
10
+ class ChatView(MethodView):
11
+ """
12
+ Handles direct access to the chat interface.
13
+ Validates if the user has an active session for the company.
14
+ """
15
+
16
+ @inject
17
+ def __init__(self,
18
+ profile_service: ProfileService,
19
+ branding_service: BrandingService,
20
+ config_service: ConfigurationService,
21
+ prompt_service: PromptService,
22
+ i18n_service: I18nService):
23
+ self.profile_service = profile_service
24
+ self.branding_service = branding_service
25
+ self.config_service = config_service
26
+ self.prompt_service = prompt_service
27
+ self.i18n_service = i18n_service
28
+
29
+ def get(self, company_short_name: str):
30
+ # 1. Validate Company
31
+ company = self.profile_service.get_company_by_short_name(company_short_name)
32
+ if not company:
33
+ return render_template('error.html',
34
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
35
+
36
+ # 2. Check Session
37
+ session_info = self.profile_service.get_current_session_info()
38
+ user_identifier = session_info.get('user_identifier')
39
+ session_company = session_info.get('company_short_name')
40
+
41
+ # If no user or session belongs to another company -> Redirect to Home
42
+ if not user_identifier or session_company != company_short_name:
43
+ return redirect(url_for('home', company_short_name=company_short_name))
44
+
45
+ # 3. Prepare Context for Chat
46
+ # (This logic mirrors the FAST PATH in BaseLoginView)
47
+ try:
48
+ branding_data = self.branding_service.get_company_branding(company_short_name)
49
+ onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
50
+ default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
51
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
52
+ js_translations = self.i18n_service.get_translation_block('js_messages')
53
+
54
+ return render_template(
55
+ "chat.html",
56
+ company_short_name=company_short_name,
57
+ user_identifier=user_identifier,
58
+ prompts=prompts,
59
+ branding=branding_data,
60
+ onboarding_cards=onboarding_cards,
61
+ js_translations=js_translations,
62
+ llm_default_model=default_llm_model,
63
+ llm_available_models=available_llm_models,
64
+ # redeem_token is None for direct access
65
+ redeem_token=None
66
+ )
67
+ except Exception as e:
68
+ # Fallback error handling
69
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
70
+ branding_data = self.branding_service.get_company_branding(company_short_name)
71
+ return render_template(
72
+ "error.html",
73
+ company_short_name=company_short_name,
74
+ branding=branding_data,
75
+ message=message
76
+ ), 500
@@ -21,7 +21,7 @@ class ForgotPasswordView(MethodView):
21
21
  self.branding_service = branding_service
22
22
  self.i18n_service = i18n_service
23
23
 
24
- self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
24
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
25
25
 
26
26
  def get(self, company_short_name: str):
27
27
  # get company info
@@ -31,9 +31,11 @@ class ForgotPasswordView(MethodView):
31
31
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
32
32
 
33
33
  branding_data = self.branding_service.get_company_branding(company_short_name)
34
+ current_lang = request.args.get("lang", "en")
34
35
  return render_template('forgot_password.html',
35
36
  company_short_name=company_short_name,
36
- branding=branding_data
37
+ branding=branding_data,
38
+ lang=current_lang
37
39
  )
38
40
 
39
41
  def post(self, company_short_name: str):
@@ -53,7 +55,9 @@ class ForgotPasswordView(MethodView):
53
55
  company_short_name=company_short_name,
54
56
  token=token, _external=True)
55
57
 
56
- response = self.profile_service.forgot_password(email=email, reset_url=reset_url)
58
+ response = self.profile_service.forgot_password(
59
+ company_short_name=company_short_name,
60
+ email=email, reset_url=reset_url)
57
61
  if "error" in response:
58
62
  flash(response["error"], 'error')
59
63
  return render_template(
@@ -63,7 +67,8 @@ class ForgotPasswordView(MethodView):
63
67
  form_data={"email": email}), 400
64
68
 
65
69
  flash(self.i18n_service.t('flash_messages.forgot_password_success'), 'success')
66
- return redirect(url_for('home', company_short_name=company_short_name))
70
+ lang = request.args.get("lang", "en")
71
+ return redirect(url_for('home', company_short_name=company_short_name, lang=lang))
67
72
 
68
73
  except Exception as e:
69
74
  flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
@@ -5,7 +5,7 @@
5
5
 
6
6
  from flask import request, jsonify
7
7
  from flask.views import MethodView
8
- from iatoolkit.services.history_service import HistoryService
8
+ from iatoolkit.services.history_manager_service import HistoryManagerService
9
9
  from iatoolkit.services.auth_service import AuthService
10
10
  from iatoolkit.services.i18n_service import I18nService
11
11
  from injector import inject
@@ -21,7 +21,7 @@ class HistoryApiView(MethodView):
21
21
  @inject
22
22
  def __init__(self,
23
23
  auth_service: AuthService,
24
- history_service: HistoryService,
24
+ history_service: HistoryManagerService,
25
25
  i18n_service: I18nService):
26
26
  self.auth_service = auth_service
27
27
  self.history_service = history_service
@@ -39,7 +39,7 @@ class HistoryApiView(MethodView):
39
39
 
40
40
  # 2. Call the history service with the unified identifier.
41
41
  # The service's signature should now only expect user_identifier.
42
- response = self.history_service.get_history(
42
+ response = self.history_service.get_full_history(
43
43
  company_short_name=company_short_name,
44
44
  user_identifier=user_identifier
45
45
  )
@@ -1,5 +1,5 @@
1
1
  # iatoolkit/views/home_view.py
2
- from flask import render_template, render_template_string
2
+ from flask import render_template, render_template_string, request
3
3
  from flask.views import MethodView
4
4
  from injector import inject
5
5
  from iatoolkit.services.profile_service import ProfileService
@@ -33,7 +33,9 @@ class HomeView(MethodView):
33
33
  message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
34
 
35
35
  branding_data = self.branding_service.get_company_branding(company_short_name)
36
- home_template = self.util.get_company_template(company_short_name, "home.html")
36
+
37
+ template_name = self.util.get_template_by_language("home")
38
+ home_template = self.util.get_company_template(company_short_name, template_name)
37
39
 
38
40
  # 2. Verificamos si el archivo de plantilla personalizado no existe.
39
41
  if not home_template:
@@ -54,7 +54,7 @@ class InitContextApiView(MethodView):
54
54
  response_message = {'status': 'OK', 'message': success_message}
55
55
 
56
56
  # if received a response ID with the context, return it
57
- if response.get('response_id'):
57
+ if response and response.get('response_id'):
58
58
  response_message['response_id'] = response['response_id']
59
59
 
60
60
  return jsonify(response_message), 200
@@ -41,18 +41,19 @@ class LLMQueryApiView(MethodView):
41
41
  result = self.query_service.llm_query(
42
42
  company_short_name=company_short_name,
43
43
  user_identifier=user_identifier,
44
+ model=data.get('model', ''),
44
45
  question=data.get('question', ''),
45
46
  prompt_name=data.get('prompt_name'),
46
47
  client_data=data.get('client_data', {}),
47
- response_id = data.get('response_id'),
48
+ ignore_history=data.get('ignore_history', False),
48
49
  files=data.get('files', [])
49
50
  )
50
51
  if 'error' in result:
51
- return jsonify(result), 407
52
+ return jsonify(result), 409
52
53
 
53
54
  return jsonify(result), 200
54
55
 
55
56
  except Exception as e:
56
57
  logging.exception(
57
58
  f"Unexpected error: {e}")
58
- return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
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
+
@@ -5,21 +5,22 @@
5
5
 
6
6
  from flask.views import MethodView
7
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
8
  from injector import inject
12
9
  import base64
13
10
 
11
+ from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
12
+ from iatoolkit.services.auth_service import AuthService
13
+ from iatoolkit.repositories.profile_repo import ProfileRepo
14
14
 
15
- class FileStoreApiView(MethodView):
15
+
16
+ class LoadDocumentApiView(MethodView):
16
17
  @inject
17
18
  def __init__(self,
18
19
  auth_service: AuthService,
19
- doc_service: LoadDocumentsService,
20
- profile_repo: ProfileRepo,):
20
+ knowledge_base_service: KnowledgeBaseService,
21
+ profile_repo: ProfileRepo):
21
22
  self.auth_service = auth_service
22
- self.doc_service = doc_service
23
+ self.knowledge_base_service = knowledge_base_service
23
24
  self.profile_repo = profile_repo
24
25
 
25
26
  def post(self):
@@ -48,18 +49,21 @@ class FileStoreApiView(MethodView):
48
49
  # get the file content from base64
49
50
  content = base64.b64decode(base64_content)
50
51
 
51
- new_document = self.doc_service._file_processing_callback(
52
+ # Use KnowledgeBaseService for ingestion
53
+ new_document = self.knowledge_base_service.ingest_document_sync(
54
+ company=company,
52
55
  filename=filename,
53
56
  content=content,
54
- company=company,
55
- context={'metadata': metadata})
57
+ metadata=metadata
58
+ )
56
59
 
57
60
  return jsonify({
58
61
  "document_id": new_document.id,
62
+ "status": "active" # ingest_document_sync returns ACTIVE on success
59
63
  }), 200
60
64
 
61
65
  except Exception as e:
62
66
  response = jsonify({"error": str(e)})
63
67
  response.status_code = 500
64
68
 
65
- return response
69
+ return response
@@ -5,12 +5,12 @@
5
5
 
6
6
  from flask.views import MethodView
7
7
  from flask import (request, redirect, render_template, url_for,
8
- render_template_string, flash)
8
+ render_template_string, flash, make_response)
9
9
  from injector import inject
10
10
  from iatoolkit.services.profile_service import ProfileService
11
11
  from iatoolkit.services.jwt_service import JWTService
12
12
  from iatoolkit.services.query_service import QueryService
13
- from iatoolkit.services.prompt_manager_service import PromptService
13
+ from iatoolkit.services.prompt_service import PromptService
14
14
  from iatoolkit.services.branding_service import BrandingService
15
15
  from iatoolkit.services.configuration_service import ConfigurationService
16
16
  from iatoolkit.services.i18n_service import I18nService
@@ -32,6 +32,7 @@ class LoginView(BaseLoginView):
32
32
  branding_data = self.branding_service.get_company_branding(company_short_name)
33
33
  email = request.form.get('email')
34
34
  password = request.form.get('password')
35
+ current_lang = request.form.get('lang') or request.args.get('lang') or 'en'
35
36
 
36
37
  # 1. Authenticate internal user
37
38
  auth_response = self.auth_service.login_local_user(
@@ -42,7 +43,14 @@ class LoginView(BaseLoginView):
42
43
 
43
44
  if not auth_response['success']:
44
45
  flash(auth_response["message"], 'error')
45
- home_template = self.utility.get_company_template(company_short_name, "home.html")
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
46
54
 
47
55
  return render_template_string(
48
56
  home_template,
@@ -57,7 +65,8 @@ class LoginView(BaseLoginView):
57
65
  # 3. define URL to call when slow path is finished
58
66
  target_url = url_for('finalize_no_token',
59
67
  company_short_name=company_short_name,
60
- _external=True)
68
+ _external=True,
69
+ lang=current_lang)
61
70
 
62
71
  # 2. Delegate the path decision to the centralized logic.
63
72
  try:
@@ -97,6 +106,9 @@ class FinalizeContextView(MethodView):
97
106
 
98
107
  def get(self, company_short_name: str, token: str = None):
99
108
  try:
109
+ # get the languaje from the query string if it exists
110
+ current_lang = request.args.get('lang') or 'en'
111
+
100
112
  session_info = self.profile_service.get_current_session_info()
101
113
  if session_info:
102
114
  # session exists, internal user
@@ -107,12 +119,12 @@ class FinalizeContextView(MethodView):
107
119
  payload = self.jwt_service.validate_chat_jwt(token)
108
120
  if not payload:
109
121
  logging.warning("Fallo crítico: No se pudo leer el auth token.")
110
- return redirect(url_for('home', company_short_name=company_short_name))
122
+ return redirect(url_for('home', company_short_name=company_short_name, lang=current_lang))
111
123
 
112
124
  user_identifier = payload.get('user_identifier')
113
125
  else:
114
126
  logging.error("missing session information or auth token")
115
- return redirect(url_for('home', company_short_name=company_short_name))
127
+ return redirect(url_for('home', company_short_name=company_short_name, lang=current_lang))
116
128
 
117
129
  company = self.profile_service.get_company_by_short_name(company_short_name)
118
130
  if not company:
@@ -121,6 +133,8 @@ class FinalizeContextView(MethodView):
121
133
  message="Empresa no encontrada"), 404
122
134
  branding_data = self.branding_service.get_company_branding(company_short_name)
123
135
 
136
+ default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
137
+
124
138
  # 2. Finalize the context rebuild (the heavy task).
125
139
  self.query_service.set_context_for_llm(
126
140
  company_short_name=company_short_name,
@@ -134,6 +148,8 @@ class FinalizeContextView(MethodView):
134
148
  # Get the entire 'js_messages' block in the correct language.
135
149
  js_translations = self.i18n_service.get_translation_block('js_messages')
136
150
 
151
+ # Importante: no envolver con make_response; dejar que Flask gestione
152
+ # tanto strings como tuplas (string, status) que pueda devolver render_template
137
153
  return render_template(
138
154
  "chat.html",
139
155
  company_short_name=company_short_name,
@@ -142,7 +158,9 @@ class FinalizeContextView(MethodView):
142
158
  prompts=prompts,
143
159
  onboarding_cards=onboarding_cards,
144
160
  js_translations=js_translations,
145
- redeem_token=token
161
+ redeem_token=token,
162
+ llm_default_model=default_llm_model,
163
+ llm_available_models=available_llm_models,
146
164
  )
147
165
 
148
166
  except Exception as e:
@@ -150,4 +168,3 @@ class FinalizeContextView(MethodView):
150
168
  company_short_name=company_short_name,
151
169
  branding=branding_data,
152
170
  message=f"An unexpected error occurred during context loading: {str(e)}"), 500
153
-
@@ -4,7 +4,7 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from flask.views import MethodView
7
- from flask import redirect, url_for, jsonify
7
+ from flask import redirect, url_for, jsonify, request, g
8
8
  from injector import inject
9
9
  from iatoolkit.services.auth_service import AuthService
10
10
  from iatoolkit.services.profile_service import ProfileService
@@ -33,7 +33,15 @@ class LogoutApiView(MethodView):
33
33
  # get URL for redirection
34
34
  url_for_redirect = company.parameters.get('external_urls', {}).get('logout_url')
35
35
  if not url_for_redirect:
36
- url_for_redirect = url_for('home', company_short_name=company_short_name)
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)
37
45
 
38
46
  # clear de session cookie
39
47
  SessionManager.clear()
@@ -5,7 +5,7 @@
5
5
 
6
6
  from flask import jsonify
7
7
  from flask.views import MethodView
8
- from iatoolkit.services.prompt_manager_service import PromptService
8
+ from iatoolkit.services.prompt_service import PromptService
9
9
  from iatoolkit.services.auth_service import AuthService
10
10
  from injector import inject
11
11
  import logging