iatoolkit 0.11.0__py3-none-any.whl → 0.71.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.
- iatoolkit/__init__.py +2 -6
- iatoolkit/base_company.py +9 -29
- iatoolkit/cli_commands.py +1 -1
- iatoolkit/common/routes.py +96 -52
- iatoolkit/common/session_manager.py +2 -1
- iatoolkit/common/util.py +17 -27
- iatoolkit/company_registry.py +1 -2
- iatoolkit/iatoolkit.py +97 -53
- iatoolkit/infra/llm_client.py +15 -20
- iatoolkit/infra/llm_proxy.py +38 -10
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/locales/en.yaml +167 -0
- iatoolkit/locales/es.yaml +163 -0
- iatoolkit/repositories/database_manager.py +23 -3
- iatoolkit/repositories/document_repo.py +1 -1
- iatoolkit/repositories/models.py +35 -10
- iatoolkit/repositories/profile_repo.py +3 -2
- iatoolkit/repositories/vs_repo.py +26 -20
- iatoolkit/services/auth_service.py +193 -0
- iatoolkit/services/branding_service.py +70 -25
- iatoolkit/services/company_context_service.py +155 -0
- iatoolkit/services/configuration_service.py +133 -0
- iatoolkit/services/dispatcher_service.py +80 -105
- iatoolkit/services/document_service.py +5 -2
- iatoolkit/services/embedding_service.py +146 -0
- iatoolkit/services/excel_service.py +30 -26
- iatoolkit/services/file_processor_service.py +4 -12
- iatoolkit/services/history_service.py +7 -16
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +18 -29
- iatoolkit/services/language_service.py +83 -0
- iatoolkit/services/load_documents_service.py +100 -113
- iatoolkit/services/mail_service.py +9 -4
- iatoolkit/services/profile_service.py +152 -76
- iatoolkit/services/prompt_manager_service.py +20 -16
- iatoolkit/services/query_service.py +208 -96
- iatoolkit/services/search_service.py +11 -4
- iatoolkit/services/sql_service.py +57 -25
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +72 -34
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +110 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +135 -222
- iatoolkit/static/js/chat_onboarding_button.js +103 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +289 -210
- iatoolkit/static/styles/chat_modal.css +63 -77
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +176 -0
- iatoolkit/system_prompts/query_main.prompt +5 -22
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +40 -20
- iatoolkit/templates/change_password.html +57 -36
- iatoolkit/templates/chat.html +180 -86
- iatoolkit/templates/chat_modals.html +138 -68
- iatoolkit/templates/error.html +44 -8
- iatoolkit/templates/forgot_password.html +40 -23
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +45 -0
- iatoolkit/templates/onboarding_shell.html +107 -0
- iatoolkit/templates/signup.html +63 -65
- iatoolkit/views/base_login_view.py +91 -0
- iatoolkit/views/change_password_view.py +56 -31
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +10 -3
- iatoolkit/views/forgot_password_view.py +27 -21
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +50 -23
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +74 -0
- iatoolkit/views/llmquery_api_view.py +58 -0
- iatoolkit/views/login_simulation_view.py +93 -0
- iatoolkit/views/login_view.py +130 -37
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
- iatoolkit/views/signup_view.py +41 -36
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +34 -29
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +41 -23
- iatoolkit-0.71.2.dist-info/RECORD +122 -0
- iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_feedback.js +0 -115
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/static/styles/chat_info.css +0 -53
- iatoolkit/templates/header.html +0 -31
- iatoolkit/templates/home.html +0 -199
- iatoolkit/templates/login.html +0 -43
- iatoolkit/templates/test.html +0 -9
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -58
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -95
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit/views/user_feedback_view.py +0 -74
- iatoolkit-0.11.0.dist-info/RECORD +0 -110
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
<span class="brand-rest"> IA</span>
|
|
42
|
+
</h1>
|
|
43
|
+
|
|
44
|
+
<div id="card-container" class="ob-card">
|
|
45
|
+
<div id="card-icon" class="ob-icon"><i class="fas fa-lightbulb"></i></div>
|
|
46
|
+
<h3 id="card-title" class="ob-title">Título de la Tarjeta</h3>
|
|
47
|
+
<p id="card-text" class="ob-text">Descripción de la tarjeta de capacitación.</p>
|
|
48
|
+
<p id="card-example" class="ob-example"></p>
|
|
49
|
+
|
|
50
|
+
<div class="ob-nav">
|
|
51
|
+
<button id="prev-card" class="ob-btn btn-branded-primary" aria-label="Anterior">
|
|
52
|
+
<i class="fas fa-chevron-left"></i>
|
|
53
|
+
</button>
|
|
54
|
+
<div id="progress-dots" class="ob-dots"></div>
|
|
55
|
+
<button id="next-card" class="ob-btn btn-branded-primary" aria-label="Siguiente">
|
|
56
|
+
<i class="fas fa-chevron-right"></i>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div id="loading-status" class="ob-loading-band">
|
|
62
|
+
<div class="spinner"></div>
|
|
63
|
+
<p>Inicializando el contexto de {{ branding.name }} para la IA...</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div id="content-container"></div>
|
|
69
|
+
</div>
|
|
70
|
+
{% endblock %}
|
|
71
|
+
|
|
72
|
+
{% block scripts %}
|
|
73
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
|
74
|
+
|
|
75
|
+
<script src="{{ url_for('static', filename='js/chat_onboarding_button.js', _external=True) }}"></script>
|
|
76
|
+
<script>
|
|
77
|
+
(function() {
|
|
78
|
+
const cardsData = {{ onboarding_cards | tojson }};
|
|
79
|
+
const iframeSrc = "{{ iframe_src_url }}";
|
|
80
|
+
|
|
81
|
+
if (!window.initOnboarding) {
|
|
82
|
+
console.error('initOnboarding no está disponible. Verifica la carga de chat_onboarding.js');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const onboarding = initOnboarding({
|
|
87
|
+
mode: 'shell',
|
|
88
|
+
cards: cardsData,
|
|
89
|
+
ui: {
|
|
90
|
+
icon: '#card-icon',
|
|
91
|
+
title: '#card-title',
|
|
92
|
+
text: '#card-text',
|
|
93
|
+
example: '#card-example',
|
|
94
|
+
dots: '#progress-dots',
|
|
95
|
+
prev: '#prev-card',
|
|
96
|
+
next: '#next-card',
|
|
97
|
+
loader: '#loader-wrapper',
|
|
98
|
+
container: '#content-container'
|
|
99
|
+
},
|
|
100
|
+
autoRotateMs: 5000,
|
|
101
|
+
shell: { iframeSrc: iframeSrc }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
onboarding.start();
|
|
105
|
+
})();
|
|
106
|
+
</script>
|
|
107
|
+
{% endblock %}
|
iatoolkit/templates/signup.html
CHANGED
|
@@ -1,78 +1,76 @@
|
|
|
1
1
|
{% extends "base.html" %}
|
|
2
2
|
|
|
3
|
-
{% block title %}
|
|
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 %}
|
|
4
12
|
|
|
5
13
|
{% block content %}
|
|
14
|
+
<div class="container mt-4">
|
|
6
15
|
|
|
16
|
+
{% include '_company_header.html' %}
|
|
7
17
|
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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>
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{% else %}
|
|
37
|
-
<div class="row">
|
|
38
|
-
<!-- En escritorio, los inputs están en la misma fila -->
|
|
39
|
-
<div class="col-md-6 mb-3">
|
|
40
|
-
<label for="first_name" class="form-label text-secondary">Nombre</label>
|
|
41
|
-
<input type="text" id="first_name" name="first_name"
|
|
42
|
-
class="form-control" required
|
|
43
|
-
value="{{ form_data.first_name if form_data else '' }}">
|
|
44
|
-
</div>
|
|
45
|
-
<div class="col-md-6 mb-3">
|
|
46
|
-
<label for="last_name" class="form-label text-secondary">Apellido</label>
|
|
47
|
-
<input type="text" id="last_name" name="last_name"
|
|
48
|
-
class="form-control" required
|
|
49
|
-
value="{{ form_data.last_name if form_data else '' }}">
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
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>
|
|
52
47
|
|
|
53
|
-
|
|
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>
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
La contraseña debe contener al menos 8 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial.
|
|
60
|
-
</small>
|
|
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
61
|
|
|
62
|
+
<!-- Botón actualizado con la clase de branding -->
|
|
63
|
+
<button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.signup.signup_button') }}</button>
|
|
64
|
+
</form>
|
|
65
|
+
<!-- Nota de privacidad -->
|
|
66
|
+
<p class="text-muted small mb-0 text-center mt-4">
|
|
67
|
+
{{ t('ui.signup.disclaimer') }}
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
62
71
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
|
|
66
|
-
</div>
|
|
67
|
-
<button type="submit" class="btn btn-primary w-100">Registrarse</button>
|
|
68
|
-
</form>
|
|
69
|
-
<!-- Nota sobre privacidad -->
|
|
70
|
-
<p class="text-muted small mb-0 text-start mt-3" style="text-align: justify;">
|
|
71
|
-
🔒 Valoramos tu privacidad. Tus datos serán utilizados exclusivamente para brindarte una mejor experiencia
|
|
72
|
-
y no serán compartidos con terceros sin tu consentimiento. Para más información, consulta nuestra
|
|
73
|
-
<a href="#" class="text-decoration-none fw-semibold">Política de Privacidad</a>.
|
|
74
|
-
</p>
|
|
75
|
-
</p>
|
|
76
|
-
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
77
74
|
</div>
|
|
75
|
+
|
|
78
76
|
{% endblock %}
|
|
@@ -0,0 +1,91 @@
|
|
|
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_manager_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
|
+
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
78
|
+
|
|
79
|
+
# Get the entire 'js_messages' block in the correct language.
|
|
80
|
+
js_translations = self.i18n_service.get_translation_block('js_messages')
|
|
81
|
+
|
|
82
|
+
return render_template(
|
|
83
|
+
"chat.html",
|
|
84
|
+
company_short_name=company_short_name,
|
|
85
|
+
user_identifier=user_identifier,
|
|
86
|
+
prompts=prompts,
|
|
87
|
+
branding=branding_data,
|
|
88
|
+
onboarding_cards=onboarding_cards,
|
|
89
|
+
js_translations=js_translations,
|
|
90
|
+
redeem_token=redeem_token
|
|
91
|
+
)
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
|
-
from flask import render_template, request
|
|
7
|
+
from flask import render_template, request, url_for, session, redirect, flash
|
|
8
8
|
from iatoolkit.services.profile_service import ProfileService
|
|
9
|
+
from iatoolkit.services.branding_service import BrandingService
|
|
10
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
9
11
|
from itsdangerous import URLSafeTimedSerializer, SignatureExpired
|
|
10
12
|
from flask_bcrypt import Bcrypt
|
|
11
13
|
from injector import inject
|
|
@@ -14,44 +16,67 @@ import os
|
|
|
14
16
|
|
|
15
17
|
class ChangePasswordView(MethodView):
|
|
16
18
|
@inject
|
|
17
|
-
def __init__(self,
|
|
19
|
+
def __init__(self,
|
|
20
|
+
profile_service: ProfileService,
|
|
21
|
+
branding_service: BrandingService,
|
|
22
|
+
i18n_service: I18nService):
|
|
18
23
|
self.profile_service = profile_service
|
|
24
|
+
self.branding_service = branding_service
|
|
25
|
+
self.i18n_service = i18n_service
|
|
19
26
|
|
|
20
27
|
self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
|
|
21
28
|
self.bcrypt = Bcrypt()
|
|
22
29
|
|
|
23
30
|
def get(self, company_short_name: str, token: str):
|
|
24
|
-
# get company info
|
|
25
|
-
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
26
|
-
if not company:
|
|
27
|
-
return render_template('error.html', message=f"Empresa no encontrada: {company_short_name}"), 404
|
|
28
|
-
|
|
29
31
|
try:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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)
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
40
61
|
|
|
41
62
|
def post(self, company_short_name: str, token: str):
|
|
42
63
|
# get company info
|
|
43
64
|
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
44
65
|
if not company:
|
|
45
|
-
return render_template('error.html',
|
|
66
|
+
return render_template('error.html',
|
|
67
|
+
message=self.i18n_service.t('errors.templates.company_not_found')), 404
|
|
46
68
|
|
|
69
|
+
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
47
70
|
try:
|
|
48
71
|
# Decodificar el token
|
|
49
72
|
email = self.serializer.loads(token, salt='password-reset', max_age=3600)
|
|
50
73
|
except SignatureExpired:
|
|
74
|
+
flash(self.i18n_service.t('errors.change_password.token_expired'), 'error')
|
|
75
|
+
|
|
51
76
|
return render_template('forgot_password.html',
|
|
52
77
|
company_short_name=company_short_name,
|
|
53
78
|
company=company,
|
|
54
|
-
|
|
79
|
+
branding=branding_data)
|
|
55
80
|
|
|
56
81
|
try:
|
|
57
82
|
# Obtener datos del formulario
|
|
@@ -67,25 +92,25 @@ class ChangePasswordView(MethodView):
|
|
|
67
92
|
)
|
|
68
93
|
|
|
69
94
|
if "error" in response:
|
|
95
|
+
flash(response["error"], 'error')
|
|
96
|
+
|
|
70
97
|
return render_template(
|
|
71
98
|
'change_password.html',
|
|
72
99
|
token=token,
|
|
73
100
|
company_short_name=company_short_name,
|
|
74
|
-
|
|
101
|
+
branding=branding_data,
|
|
75
102
|
form_data={"temp_code": temp_code,
|
|
76
103
|
"new_password": new_password,
|
|
77
|
-
"confirm_password": confirm_password},
|
|
78
|
-
alert_message=response["error"]), 400
|
|
104
|
+
"confirm_password": confirm_password}), 400
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
company_short_name=company_short_name,
|
|
83
|
-
company=company,
|
|
84
|
-
alert_icon='success',
|
|
85
|
-
alert_message="Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión.")
|
|
106
|
+
flash(self.i18n_service.t('flash_messages.password_changed_success'), 'success')
|
|
107
|
+
return redirect(url_for('home', company_short_name=company_short_name))
|
|
86
108
|
|
|
87
109
|
except Exception as e:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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,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
|
|
@@ -3,38 +3,71 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from injector import inject
|
|
8
|
-
from iatoolkit.common.auth import IAuthentication
|
|
9
|
-
from iatoolkit.services.query_service import QueryService
|
|
10
|
-
from flask import jsonify
|
|
6
|
+
import os
|
|
11
7
|
import logging
|
|
8
|
+
from flask import request, jsonify, url_for
|
|
9
|
+
from iatoolkit.views.base_login_view import BaseLoginView
|
|
12
10
|
|
|
13
|
-
class ExternalLoginView(MethodView):
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
class ExternalLoginView(BaseLoginView):
|
|
13
|
+
"""
|
|
14
|
+
Handles login for external users via API.
|
|
15
|
+
Authenticates and then delegates the path decision (fast/slow) to the base class.
|
|
16
|
+
"""
|
|
17
|
+
def post(self, company_short_name: str):
|
|
18
|
+
# Authenticate the API call.
|
|
19
|
+
auth_result = self.auth_service.verify()
|
|
20
|
+
if not auth_result.get("success"):
|
|
21
|
+
return jsonify(auth_result), auth_result.get("status_code")
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if not iaut.get("success"):
|
|
27
|
-
return jsonify(iaut), 401
|
|
23
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
24
|
+
if not company:
|
|
25
|
+
return jsonify({"error": "Empresa no encontrada"}), 404
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
user_identifier = auth_result.get('user_identifier')
|
|
28
|
+
|
|
29
|
+
# 2. Create the external user session.
|
|
30
|
+
self.profile_service.create_external_user_profile_context(company, user_identifier)
|
|
31
|
+
|
|
32
|
+
# 3. create a redeem_token for create session at the end of the process
|
|
33
|
+
redeem_token = self.jwt_service.generate_chat_jwt(
|
|
34
|
+
company_short_name=company_short_name,
|
|
35
|
+
user_identifier=user_identifier,
|
|
36
|
+
expires_delta_seconds=300
|
|
37
|
+
)
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
if not redeem_token:
|
|
40
|
+
return jsonify({"error": "Error al generar el redeem_token para login externo."}), 403
|
|
41
|
+
|
|
42
|
+
# 4. define URL to call when slow path is finished
|
|
43
|
+
target_url = url_for('finalize_with_token',
|
|
44
|
+
company_short_name=company_short_name,
|
|
45
|
+
token=redeem_token,
|
|
46
|
+
_external=True)
|
|
47
|
+
|
|
48
|
+
# 5. Delegate the path decision to the centralized logic.
|
|
49
|
+
try:
|
|
50
|
+
return self._handle_login_path(company_short_name, user_identifier, target_url, redeem_token)
|
|
37
51
|
except Exception as e:
|
|
38
|
-
logging.exception(
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
|
|
53
|
+
return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RedeemTokenApiView(BaseLoginView):
|
|
57
|
+
# this endpoint is only used ONLY by chat_main.js to redeem a chat token
|
|
58
|
+
def post(self, company_short_name: str):
|
|
59
|
+
data = request.get_json()
|
|
60
|
+
if not data or 'token' not in data:
|
|
61
|
+
return jsonify({"error": "Falta token de validación"}), 400
|
|
62
|
+
|
|
63
|
+
# get the token and validate with auth service
|
|
64
|
+
token = data.get('token')
|
|
65
|
+
redeem_result = self.auth_service.redeem_token_for_session(
|
|
66
|
+
company_short_name=company_short_name,
|
|
67
|
+
token=token
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not redeem_result['success']:
|
|
71
|
+
return {"error": redeem_result['error']}, 401
|
|
72
|
+
|
|
73
|
+
return {"status": "ok"}, 200
|