iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (150) hide show
  1. iatoolkit/__init__.py +27 -35
  2. iatoolkit/base_company.py +3 -35
  3. iatoolkit/cli_commands.py +18 -47
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +48 -0
  6. iatoolkit/common/interfaces/__init__.py +0 -0
  7. iatoolkit/common/interfaces/asset_storage.py +34 -0
  8. iatoolkit/common/interfaces/database_provider.py +39 -0
  9. iatoolkit/common/model_registry.py +159 -0
  10. iatoolkit/common/routes.py +138 -0
  11. iatoolkit/common/session_manager.py +26 -0
  12. iatoolkit/common/util.py +353 -0
  13. iatoolkit/company_registry.py +66 -29
  14. iatoolkit/core.py +514 -0
  15. iatoolkit/infra/__init__.py +5 -0
  16. iatoolkit/infra/brevo_mail_app.py +123 -0
  17. iatoolkit/infra/call_service.py +140 -0
  18. iatoolkit/infra/connectors/__init__.py +5 -0
  19. iatoolkit/infra/connectors/file_connector.py +17 -0
  20. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  21. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  22. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  23. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  24. iatoolkit/infra/connectors/s3_connector.py +33 -0
  25. iatoolkit/infra/google_chat_app.py +57 -0
  26. iatoolkit/infra/llm_providers/__init__.py +0 -0
  27. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  28. iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
  29. iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
  30. iatoolkit/infra/llm_proxy.py +268 -0
  31. iatoolkit/infra/llm_response.py +45 -0
  32. iatoolkit/infra/redis_session_manager.py +122 -0
  33. iatoolkit/locales/en.yaml +222 -0
  34. iatoolkit/locales/es.yaml +225 -0
  35. iatoolkit/repositories/__init__.py +5 -0
  36. iatoolkit/repositories/database_manager.py +187 -0
  37. iatoolkit/repositories/document_repo.py +33 -0
  38. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  39. iatoolkit/repositories/llm_query_repo.py +105 -0
  40. iatoolkit/repositories/models.py +279 -0
  41. iatoolkit/repositories/profile_repo.py +171 -0
  42. iatoolkit/repositories/vs_repo.py +150 -0
  43. iatoolkit/services/__init__.py +5 -0
  44. iatoolkit/services/auth_service.py +193 -0
  45. {services → iatoolkit/services}/benchmark_service.py +7 -7
  46. iatoolkit/services/branding_service.py +153 -0
  47. iatoolkit/services/company_context_service.py +214 -0
  48. iatoolkit/services/configuration_service.py +375 -0
  49. iatoolkit/services/dispatcher_service.py +134 -0
  50. {services → iatoolkit/services}/document_service.py +20 -8
  51. iatoolkit/services/embedding_service.py +148 -0
  52. iatoolkit/services/excel_service.py +156 -0
  53. {services → iatoolkit/services}/file_processor_service.py +36 -21
  54. iatoolkit/services/history_manager_service.py +208 -0
  55. iatoolkit/services/i18n_service.py +104 -0
  56. iatoolkit/services/jwt_service.py +80 -0
  57. iatoolkit/services/language_service.py +89 -0
  58. iatoolkit/services/license_service.py +82 -0
  59. iatoolkit/services/llm_client_service.py +438 -0
  60. iatoolkit/services/load_documents_service.py +174 -0
  61. iatoolkit/services/mail_service.py +213 -0
  62. {services → iatoolkit/services}/profile_service.py +200 -101
  63. iatoolkit/services/prompt_service.py +303 -0
  64. iatoolkit/services/query_service.py +467 -0
  65. iatoolkit/services/search_service.py +55 -0
  66. iatoolkit/services/sql_service.py +169 -0
  67. iatoolkit/services/tool_service.py +246 -0
  68. iatoolkit/services/user_feedback_service.py +117 -0
  69. iatoolkit/services/user_session_context_service.py +213 -0
  70. iatoolkit/static/images/fernando.jpeg +0 -0
  71. iatoolkit/static/images/iatoolkit_core.png +0 -0
  72. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  73. iatoolkit/static/js/chat_feedback_button.js +80 -0
  74. iatoolkit/static/js/chat_filepond.js +85 -0
  75. iatoolkit/static/js/chat_help_content.js +124 -0
  76. iatoolkit/static/js/chat_history_button.js +110 -0
  77. iatoolkit/static/js/chat_logout_button.js +36 -0
  78. iatoolkit/static/js/chat_main.js +401 -0
  79. iatoolkit/static/js/chat_model_selector.js +227 -0
  80. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  81. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  82. iatoolkit/static/js/chat_reload_button.js +38 -0
  83. iatoolkit/static/styles/chat_iatoolkit.css +559 -0
  84. iatoolkit/static/styles/chat_modal.css +133 -0
  85. iatoolkit/static/styles/chat_public.css +135 -0
  86. iatoolkit/static/styles/documents.css +598 -0
  87. iatoolkit/static/styles/landing_page.css +398 -0
  88. iatoolkit/static/styles/llm_output.css +148 -0
  89. iatoolkit/static/styles/onboarding.css +176 -0
  90. iatoolkit/system_prompts/__init__.py +0 -0
  91. iatoolkit/system_prompts/query_main.prompt +30 -23
  92. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  93. iatoolkit/templates/_company_header.html +45 -0
  94. iatoolkit/templates/_login_widget.html +42 -0
  95. iatoolkit/templates/base.html +78 -0
  96. iatoolkit/templates/change_password.html +66 -0
  97. iatoolkit/templates/chat.html +337 -0
  98. iatoolkit/templates/chat_modals.html +185 -0
  99. iatoolkit/templates/error.html +51 -0
  100. iatoolkit/templates/forgot_password.html +51 -0
  101. iatoolkit/templates/onboarding_shell.html +106 -0
  102. iatoolkit/templates/signup.html +79 -0
  103. iatoolkit/views/__init__.py +5 -0
  104. iatoolkit/views/base_login_view.py +96 -0
  105. iatoolkit/views/change_password_view.py +116 -0
  106. iatoolkit/views/chat_view.py +76 -0
  107. iatoolkit/views/embedding_api_view.py +65 -0
  108. iatoolkit/views/forgot_password_view.py +75 -0
  109. iatoolkit/views/help_content_api_view.py +54 -0
  110. iatoolkit/views/history_api_view.py +56 -0
  111. iatoolkit/views/home_view.py +63 -0
  112. iatoolkit/views/init_context_api_view.py +74 -0
  113. iatoolkit/views/llmquery_api_view.py +59 -0
  114. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  115. iatoolkit/views/load_document_api_view.py +65 -0
  116. iatoolkit/views/login_view.py +170 -0
  117. iatoolkit/views/logout_api_view.py +57 -0
  118. iatoolkit/views/profile_api_view.py +46 -0
  119. iatoolkit/views/prompt_api_view.py +37 -0
  120. iatoolkit/views/root_redirect_view.py +22 -0
  121. iatoolkit/views/signup_view.py +100 -0
  122. iatoolkit/views/static_page_view.py +27 -0
  123. iatoolkit/views/user_feedback_api_view.py +60 -0
  124. iatoolkit/views/users_api_view.py +33 -0
  125. iatoolkit/views/verify_user_view.py +60 -0
  126. iatoolkit-0.107.4.dist-info/METADATA +268 -0
  127. iatoolkit-0.107.4.dist-info/RECORD +132 -0
  128. iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
  129. iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  130. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
  131. iatoolkit/iatoolkit.py +0 -413
  132. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  133. iatoolkit-0.3.9.dist-info/METADATA +0 -252
  134. iatoolkit-0.3.9.dist-info/RECORD +0 -32
  135. services/__init__.py +0 -5
  136. services/api_service.py +0 -75
  137. services/dispatcher_service.py +0 -351
  138. services/excel_service.py +0 -98
  139. services/history_service.py +0 -45
  140. services/jwt_service.py +0 -91
  141. services/load_documents_service.py +0 -212
  142. services/mail_service.py +0 -62
  143. services/prompt_manager_service.py +0 -172
  144. services/query_service.py +0 -334
  145. services/search_service.py +0 -32
  146. services/sql_service.py +0 -42
  147. services/tasks_service.py +0 -188
  148. services/user_feedback_service.py +0 -67
  149. services/user_session_context_service.py +0 -85
  150. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,106 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Iniciando {{ branding.name }} IA...{% endblock %}
4
+
5
+ {% block styles %}
6
+ {% endblock %}
7
+
8
+ {% block content %}
9
+
10
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/llm_output.css', _external=True) }}?v=6">
11
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/onboarding.css', _external=True) }}?v=6">
12
+
13
+ <style>
14
+ {% if branding and branding.css_variables %}
15
+ {{ branding.css_variables|safe }}
16
+ {% endif %}
17
+
18
+ {# 2. Ahora definimos las reglas que usan esas variables #}
19
+ .onboarding-shell-root { position: relative; height: 100vh; }
20
+ .onboarding-shell-root #loader-wrapper { position: absolute; inset: 0; background: #f4f7f6; z-index: 1000; display:flex; align-items:center; justify-content:center; padding:20px; box-sizing:border-box; transition: opacity .5s; }
21
+ .onboarding-shell-root .ob-stack { width:100%; max-width:520px; display:flex; flex-direction:column; gap:16px; }
22
+ .onboarding-shell-root .ob-loading-band { display:flex; align-items:center; justify-content:center; gap:12px; padding:12px 14px; background:#fff; border-radius:12px; box-shadow:0 4px 20px rgba(0,0,0,.08); }
23
+ .onboarding-shell-root .spinner { width:28px; height:28px; border:4px solid rgba(0,0,0,.1); border-top-color: var(--brand-primary-color, #FF5100); border-radius:50%; animation: spin 1s linear infinite; }
24
+ @keyframes spin { to { transform: rotate(360deg); } }
25
+ .onboarding-shell-root #content-container { width:100%; height:100%; }
26
+ .onboarding-shell-root #content-container iframe { width:100%; height:100%; border:none; }
27
+
28
+ /* Forzar colores de marca (sube especificidad y evita overrides) */
29
+ .onboarding-shell-root .ob-brand-header { color: var(--brand-secondary-color, #06326B) !important; }
30
+ .onboarding-shell-root .ob-brand-header .brand-name { color: var(--brand-primary-color, #FF5100) !important; }
31
+ .onboarding-shell-root .ob-icon { color: var(--brand-primary-color, #FF5100) !important; }
32
+ .onboarding-shell-root .ob-btn { background-color: var(--brand-secondary-color, #06326B) !important; color: var(--brand-text-on-secondary, #FFFFFF) !important; border:none !important; }
33
+ .onboarding-shell-root .ob-dots > div.active { background-color: var(--brand-primary-color, #FF5100) !important; }
34
+ </style>
35
+
36
+ <div class="onboarding-shell-root ob-root">
37
+ <div id="loader-wrapper">
38
+ <div class="ob-stack">
39
+ <h1 id="ob-brand-header" class="ob-brand-header">
40
+ <span class="brand-name text-brand-primary">{{ branding.name | default('IAToolkit') }}</span>
41
+ </h1>
42
+
43
+ <div id="card-container" class="ob-card">
44
+ <div id="card-icon" class="ob-icon"><i class="fas fa-lightbulb"></i></div>
45
+ <h3 id="card-title" class="ob-title">Título de la Tarjeta</h3>
46
+ <p id="card-text" class="ob-text">Descripción de la tarjeta de capacitación.</p>
47
+ <p id="card-example" class="ob-example"></p>
48
+
49
+ <div class="ob-nav">
50
+ <button id="prev-card" class="ob-btn btn-branded-primary" aria-label="Anterior">
51
+ <i class="fas fa-chevron-left"></i>
52
+ </button>
53
+ <div id="progress-dots" class="ob-dots"></div>
54
+ <button id="next-card" class="ob-btn btn-branded-primary" aria-label="Siguiente">
55
+ <i class="fas fa-chevron-right"></i>
56
+ </button>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="loading-status" class="ob-loading-band">
61
+ <div class="spinner"></div>
62
+ <p>{{ t('ui.chat.init_context') }} </p>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div id="content-container"></div>
68
+ </div>
69
+ {% endblock %}
70
+
71
+ {% block scripts %}
72
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
73
+
74
+ <script src="{{ url_for('static', filename='js/chat_onboarding_button.js', _external=True) }}"></script>
75
+ <script>
76
+ (function() {
77
+ const cardsData = {{ onboarding_cards | tojson }};
78
+ const iframeSrc = "{{ iframe_src_url }}";
79
+
80
+ if (!window.initOnboarding) {
81
+ console.error('initOnboarding no está disponible. Verifica la carga de chat_onboarding.js');
82
+ return;
83
+ }
84
+
85
+ const onboarding = initOnboarding({
86
+ mode: 'shell',
87
+ cards: cardsData,
88
+ ui: {
89
+ icon: '#card-icon',
90
+ title: '#card-title',
91
+ text: '#card-text',
92
+ example: '#card-example',
93
+ dots: '#progress-dots',
94
+ prev: '#prev-card',
95
+ next: '#next-card',
96
+ loader: '#loader-wrapper',
97
+ container: '#content-container'
98
+ },
99
+ autoRotateMs: 5000,
100
+ shell: { iframeSrc: iframeSrc }
101
+ });
102
+
103
+ onboarding.start();
104
+ })();
105
+ </script>
106
+ {% endblock %}
@@ -0,0 +1,79 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ t('ui.signup.title') }} - {{ branding.name }}{% endblock %}
4
+
5
+ {% block styles %}
6
+ {# ¡Importante! Añadimos los estilos para el branding #}
7
+ <style>
8
+ {{ branding.css_variables | safe }}
9
+ </style>
10
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_public.css') }}">
11
+ {% endblock %}
12
+
13
+ {% block content %}
14
+ <div class="container mt-4">
15
+
16
+ {% include '_company_header.html' %}
17
+
18
+ <section class="hero-section">
19
+ <div class="container">
20
+ <div class="row justify-content-center">
21
+ <!-- Se ha reducido el ancho de la columna a lg-6 y md-8 -->
22
+ <div class="col-lg-6 col-md-8">
23
+ <div class="branded-form-container">
24
+ <h4 class="branded-form-title">{{ t('ui.signup.title') }}</h4>
25
+ <form action="{{ url_for('signup', company_short_name=company_short_name) }}" method="post">
26
+ <div class="mb-3">
27
+ <label for="email" class="form-label text-secondary">{{ t('ui.signup.email_label') }}</label>
28
+ <input type="email" autocomplete="off" id="email" name="email"
29
+ class="form-control" required
30
+ value="{{ form_data.email if form_data else '' }}">
31
+ </div>
32
+
33
+ <div class="row">
34
+ <div class="col-md-6 mb-3">
35
+ <label for="first_name" class="form-label text-secondary">{{ t('ui.signup.first_name_label') }}</label>
36
+ <input type="text" id="first_name" name="first_name"
37
+ class="form-control" required
38
+ value="{{ form_data.first_name if form_data else '' }}">
39
+ </div>
40
+ <div class="col-md-6 mb-3">
41
+ <label for="last_name" class="form-label text-secondary">{{ t('ui.signup.last_name_label') }}</label>
42
+ <input type="text" id="last_name" name="last_name"
43
+ class="form-control" required
44
+ value="{{ form_data.last_name if form_data else '' }}">
45
+ </div>
46
+ </div>
47
+
48
+ <div class="mb-3">
49
+ <label for="password" class="form-label text-secondary">{{ t('ui.signup.password_label') }}</label>
50
+ <input type="password" id="password" name="password" class="form-control" required>
51
+ <div class="d-flex align-items-start text-muted mt-2" style="font-size: 0.8rem;">
52
+ <i class="bi bi-info-circle me-2" style="font-size: 0.9rem; line-height: 1.4;"></i>
53
+ <span>{{ t('ui.change_password.password_instructions') }}</span>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="mb-3">
58
+ <label for="confirm_password" class="form-label text-secondary">{{ t('ui.signup.confirm_password_label') }}</label>
59
+ <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
60
+ </div>
61
+
62
+ <!-- language: html -->
63
+ <input type="hidden" name="lang" value="{{ lang or 'en' }}">
64
+
65
+ <!-- Botón actualizado con la clase de branding -->
66
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.signup.signup_button') }}</button>
67
+ </form>
68
+ <!-- Nota de privacidad -->
69
+ <p class="text-muted small mb-0 text-center mt-4">
70
+ {{ t('ui.signup.disclaimer') }}
71
+ </p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </section>
77
+ </div>
78
+
79
+ {% endblock %}
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
@@ -0,0 +1,96 @@
1
+ # iatoolkit/views/base_login_view.py
2
+ # Copyright (c) 2024 Fernando Libedinsky
3
+ # Product: IAToolkit
4
+ #
5
+ # IAToolkit is open source software.
6
+
7
+ from flask.views import MethodView
8
+ from flask import render_template, url_for
9
+ from injector import inject
10
+ from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.services.auth_service import AuthService
12
+ from iatoolkit.services.query_service import QueryService
13
+ from iatoolkit.services.branding_service import BrandingService
14
+ from iatoolkit.services.configuration_service import ConfigurationService
15
+ from iatoolkit.services.prompt_service import PromptService
16
+ from iatoolkit.services.i18n_service import I18nService
17
+ from iatoolkit.common.util import Utility
18
+ from iatoolkit.services.jwt_service import JWTService
19
+ from iatoolkit.repositories.models import Company
20
+
21
+
22
+ class BaseLoginView(MethodView):
23
+ """
24
+ Base class for views that initiate a session and decide the context
25
+ loading path (fast or slow).
26
+ """
27
+ @inject
28
+ def __init__(self,
29
+ profile_service: ProfileService,
30
+ auth_service: AuthService,
31
+ jwt_service: JWTService,
32
+ branding_service: BrandingService,
33
+ prompt_service: PromptService,
34
+ config_service: ConfigurationService,
35
+ query_service: QueryService,
36
+ i18n_service: I18nService,
37
+ utility: Utility
38
+ ):
39
+ self.profile_service = profile_service
40
+ self.auth_service = auth_service
41
+ self.jwt_service = jwt_service
42
+ self.branding_service = branding_service
43
+ self.prompt_service = prompt_service
44
+ self.config_service = config_service
45
+ self.query_service = query_service
46
+ self.i18n_service = i18n_service
47
+ self.utility = utility
48
+
49
+
50
+ def _handle_login_path(self,
51
+ company_short_name: str,
52
+ user_identifier: str,
53
+ target_url: str,
54
+ redeem_token: str = None):
55
+ """
56
+ Centralized logic to decide between the fast path and the slow path.
57
+ """
58
+ # --- Get the company branding and onboarding_cards
59
+ branding_data = self.branding_service.get_company_branding(company_short_name)
60
+ onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
61
+
62
+ # this service decides is the context needs to be rebuilt or not
63
+ prep_result = self.query_service.prepare_context(
64
+ company_short_name=company_short_name, user_identifier=user_identifier
65
+ )
66
+
67
+ if prep_result.get('rebuild_needed'):
68
+ # --- SLOW PATH: Render the loading shell ---
69
+ return render_template(
70
+ "onboarding_shell.html",
71
+ iframe_src_url=target_url,
72
+ branding=branding_data,
73
+ onboarding_cards=onboarding_cards
74
+ )
75
+ else:
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
+
80
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
81
+
82
+ # Get the entire 'js_messages' block in the correct language.
83
+ js_translations = self.i18n_service.get_translation_block('js_messages')
84
+
85
+ return render_template(
86
+ "chat.html",
87
+ company_short_name=company_short_name,
88
+ user_identifier=user_identifier,
89
+ prompts=prompts,
90
+ branding=branding_data,
91
+ onboarding_cards=onboarding_cards,
92
+ js_translations=js_translations,
93
+ redeem_token=redeem_token,
94
+ llm_default_model=default_llm_model,
95
+ llm_available_models = available_llm_models,
96
+ )
@@ -0,0 +1,116 @@
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 render_template, request, url_for, session, redirect, flash
8
+ from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.branding_service import BrandingService
10
+ from iatoolkit.services.i18n_service import I18nService
11
+ from itsdangerous import URLSafeTimedSerializer, SignatureExpired
12
+ from flask_bcrypt import Bcrypt
13
+ from injector import inject
14
+ import os
15
+
16
+
17
+ class ChangePasswordView(MethodView):
18
+ @inject
19
+ def __init__(self,
20
+ profile_service: ProfileService,
21
+ branding_service: BrandingService,
22
+ i18n_service: I18nService):
23
+ self.profile_service = profile_service
24
+ self.branding_service = branding_service
25
+ self.i18n_service = i18n_service
26
+
27
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
28
+ self.bcrypt = Bcrypt()
29
+
30
+ def get(self, company_short_name: str, token: str):
31
+ try:
32
+ company = self.profile_service.get_company_by_short_name(company_short_name)
33
+ if not company:
34
+ return render_template('error.html',
35
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
36
+
37
+ branding_data = self.branding_service.get_company_branding(company_short_name)
38
+
39
+ try:
40
+ # Decodificar el token
41
+ email = self.serializer.loads(token, salt='password-reset', max_age=3600)
42
+ except SignatureExpired as e:
43
+ flash(self.i18n_service.t('errors.change_password.token_expired'), 'error')
44
+ return render_template('forgot_password.html',
45
+ branding=branding_data)
46
+
47
+ return render_template('change_password.html',
48
+ company_short_name=company_short_name,
49
+ company=company,
50
+ branding=branding_data,
51
+ token=token,
52
+ email=email)
53
+ except Exception as e:
54
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
55
+ return render_template(
56
+ "error.html",
57
+ company_short_name=company_short_name,
58
+ branding=branding_data,
59
+ message=message
60
+ ), 500
61
+
62
+ def post(self, company_short_name: str, token: str):
63
+ # get company info
64
+ company = self.profile_service.get_company_by_short_name(company_short_name)
65
+ if not company:
66
+ return render_template('error.html',
67
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
68
+
69
+ branding_data = self.branding_service.get_company_branding(company_short_name)
70
+ try:
71
+ # Decodificar el token
72
+ email = self.serializer.loads(token, salt='password-reset', max_age=3600)
73
+ except SignatureExpired:
74
+ flash(self.i18n_service.t('errors.change_password.token_expired'), 'error')
75
+
76
+ return render_template('forgot_password.html',
77
+ company_short_name=company_short_name,
78
+ company=company,
79
+ branding=branding_data)
80
+
81
+ try:
82
+ # Obtener datos del formulario
83
+ temp_code = request.form.get('temp_code')
84
+ new_password = request.form.get('new_password')
85
+ confirm_password = request.form.get('confirm_password')
86
+
87
+ response = self.profile_service.change_password(
88
+ email=email,
89
+ temp_code=temp_code,
90
+ new_password=new_password,
91
+ confirm_password=confirm_password
92
+ )
93
+
94
+ if "error" in response:
95
+ flash(response["error"], 'error')
96
+
97
+ return render_template(
98
+ 'change_password.html',
99
+ token=token,
100
+ company_short_name=company_short_name,
101
+ branding=branding_data,
102
+ form_data={"temp_code": temp_code,
103
+ "new_password": new_password,
104
+ "confirm_password": confirm_password}), 400
105
+
106
+ flash(self.i18n_service.t('flash_messages.password_changed_success'), 'success')
107
+ return redirect(url_for('home', company_short_name=company_short_name))
108
+
109
+ except Exception as e:
110
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
111
+ return render_template(
112
+ "error.html",
113
+ company_short_name=company_short_name,
114
+ branding=branding_data,
115
+ message=message
116
+ ), 500
@@ -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
@@ -0,0 +1,65 @@
1
+ # iatoolkit/views/embedding_api_view.py
2
+ # Copyright (c) 2024 Fernando Libedinsky
3
+ # Product: IAToolkit
4
+ #
5
+ # IAToolkit is open source software.
6
+
7
+ from flask import request, jsonify
8
+ from flask.views import MethodView
9
+ from iatoolkit.services.embedding_service import EmbeddingService
10
+ from iatoolkit.services.auth_service import AuthService
11
+ from injector import inject
12
+ import logging
13
+
14
+ class EmbeddingApiView(MethodView):
15
+ """
16
+ Handles API requests to generate an embedding for a given text.
17
+ Authentication is based on the active Flask session.
18
+ """
19
+
20
+ @inject
21
+ def __init__(self,
22
+ auth_service: AuthService,
23
+ embedding_service: EmbeddingService):
24
+ self.auth_service = auth_service
25
+ self.embedding_service = embedding_service
26
+
27
+ def post(self, company_short_name: str):
28
+ """
29
+ Generates an embedding for the text provided in the request body.
30
+ Expects a JSON payload with a "text" key.
31
+ """
32
+ try:
33
+ # 1. Authenticate the user from the current session
34
+ auth_result = self.auth_service.verify(anonymous=True)
35
+ if not auth_result.get("success"):
36
+ return jsonify(auth_result), auth_result.get("status_code", 401)
37
+
38
+ # 2. Validate incoming request data
39
+ if not request.is_json:
40
+ return jsonify({"error": "Request must be JSON"}), 400
41
+
42
+ data = request.get_json()
43
+ text = data.get('text')
44
+
45
+ if not text:
46
+ return jsonify({"error": "The 'text' key is required."}), 400
47
+
48
+ # 3. Call the embedding service, now passing the company_short_name
49
+ embedding_b64 = self.embedding_service.embed_text(
50
+ company_short_name=company_short_name,
51
+ text=text,
52
+ to_base64=True
53
+ )
54
+
55
+ model_name = self.embedding_service.get_model_name(company_short_name)
56
+ response = {
57
+ "embedding": embedding_b64,
58
+ "model": model_name
59
+ }
60
+ return jsonify(response), 200
61
+
62
+ except Exception as e:
63
+ logging.exception(f"Unexpected error in EmbeddingApiView: {e}")
64
+ # Return a generic error message to the client
65
+ return jsonify({"error": "An internal error occurred while generating the embedding."}), 500
@@ -0,0 +1,75 @@
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 render_template, request, url_for, redirect, session, flash
8
+ from injector import inject
9
+ from iatoolkit.services.profile_service import ProfileService
10
+ from iatoolkit.services.branding_service import BrandingService
11
+ from iatoolkit.services.i18n_service import I18nService
12
+ from itsdangerous import URLSafeTimedSerializer
13
+ import os
14
+
15
+ class ForgotPasswordView(MethodView):
16
+ @inject
17
+ def __init__(self, profile_service: ProfileService,
18
+ branding_service: BrandingService,
19
+ i18n_service: I18nService):
20
+ self.profile_service = profile_service
21
+ self.branding_service = branding_service
22
+ self.i18n_service = i18n_service
23
+
24
+ self.serializer = URLSafeTimedSerializer(os.getenv("IATOOLKIT_SECRET_KEY"))
25
+
26
+ def get(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',
31
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
32
+
33
+ branding_data = self.branding_service.get_company_branding(company_short_name)
34
+ current_lang = request.args.get("lang", "en")
35
+ return render_template('forgot_password.html',
36
+ company_short_name=company_short_name,
37
+ branding=branding_data,
38
+ lang=current_lang
39
+ )
40
+
41
+ def post(self, company_short_name: str):
42
+
43
+ try:
44
+ company = self.profile_service.get_company_by_short_name(company_short_name)
45
+ if not company:
46
+ return render_template('error.html',
47
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
48
+
49
+ branding_data = self.branding_service.get_company_branding(company_short_name)
50
+ email = request.form.get('email')
51
+
52
+ # create a safe token and url for it
53
+ token = self.serializer.dumps(email, salt='password-reset')
54
+ reset_url = url_for('change_password',
55
+ company_short_name=company_short_name,
56
+ token=token, _external=True)
57
+
58
+ response = self.profile_service.forgot_password(
59
+ company_short_name=company_short_name,
60
+ email=email, reset_url=reset_url)
61
+ if "error" in response:
62
+ flash(response["error"], 'error')
63
+ return render_template(
64
+ 'forgot_password.html',
65
+ company_short_name=company_short_name,
66
+ branding=branding_data,
67
+ form_data={"email": email}), 400
68
+
69
+ flash(self.i18n_service.t('flash_messages.forgot_password_success'), 'success')
70
+ lang = request.args.get("lang", "en")
71
+ return redirect(url_for('home', company_short_name=company_short_name, lang=lang))
72
+
73
+ except Exception as e:
74
+ flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
75
+ return redirect(url_for('home', company_short_name=company_short_name))