iatoolkit 0.4.2__py3-none-any.whl → 0.66.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 (123) hide show
  1. iatoolkit/__init__.py +13 -35
  2. iatoolkit/base_company.py +74 -8
  3. iatoolkit/cli_commands.py +15 -23
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +46 -0
  6. iatoolkit/common/routes.py +141 -0
  7. iatoolkit/common/session_manager.py +24 -0
  8. iatoolkit/common/util.py +348 -0
  9. iatoolkit/company_registry.py +7 -8
  10. iatoolkit/iatoolkit.py +169 -96
  11. iatoolkit/infra/__init__.py +5 -0
  12. iatoolkit/infra/call_service.py +140 -0
  13. iatoolkit/infra/connectors/__init__.py +5 -0
  14. iatoolkit/infra/connectors/file_connector.py +17 -0
  15. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  16. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  17. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  18. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  19. iatoolkit/infra/connectors/s3_connector.py +33 -0
  20. iatoolkit/infra/gemini_adapter.py +356 -0
  21. iatoolkit/infra/google_chat_app.py +57 -0
  22. iatoolkit/infra/llm_client.py +429 -0
  23. iatoolkit/infra/llm_proxy.py +139 -0
  24. iatoolkit/infra/llm_response.py +40 -0
  25. iatoolkit/infra/mail_app.py +145 -0
  26. iatoolkit/infra/openai_adapter.py +90 -0
  27. iatoolkit/infra/redis_session_manager.py +122 -0
  28. iatoolkit/locales/en.yaml +144 -0
  29. iatoolkit/locales/es.yaml +140 -0
  30. iatoolkit/repositories/__init__.py +5 -0
  31. iatoolkit/repositories/database_manager.py +110 -0
  32. iatoolkit/repositories/document_repo.py +33 -0
  33. iatoolkit/repositories/llm_query_repo.py +91 -0
  34. iatoolkit/repositories/models.py +336 -0
  35. iatoolkit/repositories/profile_repo.py +123 -0
  36. iatoolkit/repositories/tasks_repo.py +52 -0
  37. iatoolkit/repositories/vs_repo.py +139 -0
  38. iatoolkit/services/__init__.py +5 -0
  39. iatoolkit/services/auth_service.py +193 -0
  40. {services → iatoolkit/services}/benchmark_service.py +6 -6
  41. iatoolkit/services/branding_service.py +149 -0
  42. {services → iatoolkit/services}/dispatcher_service.py +39 -99
  43. {services → iatoolkit/services}/document_service.py +5 -5
  44. {services → iatoolkit/services}/excel_service.py +27 -21
  45. {services → iatoolkit/services}/file_processor_service.py +5 -5
  46. iatoolkit/services/help_content_service.py +30 -0
  47. {services → iatoolkit/services}/history_service.py +8 -16
  48. iatoolkit/services/i18n_service.py +104 -0
  49. {services → iatoolkit/services}/jwt_service.py +18 -27
  50. iatoolkit/services/language_service.py +77 -0
  51. {services → iatoolkit/services}/load_documents_service.py +19 -14
  52. {services → iatoolkit/services}/mail_service.py +5 -5
  53. iatoolkit/services/onboarding_service.py +43 -0
  54. {services → iatoolkit/services}/profile_service.py +155 -89
  55. {services → iatoolkit/services}/prompt_manager_service.py +26 -11
  56. {services → iatoolkit/services}/query_service.py +142 -104
  57. {services → iatoolkit/services}/search_service.py +21 -5
  58. {services → iatoolkit/services}/sql_service.py +24 -6
  59. {services → iatoolkit/services}/tasks_service.py +10 -10
  60. iatoolkit/services/user_feedback_service.py +103 -0
  61. iatoolkit/services/user_session_context_service.py +143 -0
  62. iatoolkit/static/images/fernando.jpeg +0 -0
  63. iatoolkit/static/js/chat_feedback_button.js +80 -0
  64. iatoolkit/static/js/chat_filepond.js +85 -0
  65. iatoolkit/static/js/chat_help_content.js +124 -0
  66. iatoolkit/static/js/chat_history_button.js +112 -0
  67. iatoolkit/static/js/chat_logout_button.js +36 -0
  68. iatoolkit/static/js/chat_main.js +364 -0
  69. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  70. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  71. iatoolkit/static/js/chat_reload_button.js +35 -0
  72. iatoolkit/static/styles/chat_iatoolkit.css +592 -0
  73. iatoolkit/static/styles/chat_modal.css +169 -0
  74. iatoolkit/static/styles/chat_public.css +107 -0
  75. iatoolkit/static/styles/landing_page.css +182 -0
  76. iatoolkit/static/styles/llm_output.css +115 -0
  77. iatoolkit/static/styles/onboarding.css +169 -0
  78. iatoolkit/system_prompts/query_main.prompt +5 -15
  79. iatoolkit/templates/_company_header.html +20 -0
  80. iatoolkit/templates/_login_widget.html +42 -0
  81. iatoolkit/templates/about.html +13 -0
  82. iatoolkit/templates/base.html +65 -0
  83. iatoolkit/templates/change_password.html +66 -0
  84. iatoolkit/templates/chat.html +287 -0
  85. iatoolkit/templates/chat_modals.html +181 -0
  86. iatoolkit/templates/error.html +51 -0
  87. iatoolkit/templates/forgot_password.html +50 -0
  88. iatoolkit/templates/index.html +145 -0
  89. iatoolkit/templates/login_simulation.html +34 -0
  90. iatoolkit/templates/onboarding_shell.html +104 -0
  91. iatoolkit/templates/signup.html +76 -0
  92. iatoolkit/views/__init__.py +5 -0
  93. iatoolkit/views/base_login_view.py +92 -0
  94. iatoolkit/views/change_password_view.py +117 -0
  95. iatoolkit/views/external_login_view.py +73 -0
  96. iatoolkit/views/file_store_api_view.py +65 -0
  97. iatoolkit/views/forgot_password_view.py +72 -0
  98. iatoolkit/views/help_content_api_view.py +54 -0
  99. iatoolkit/views/history_api_view.py +56 -0
  100. iatoolkit/views/home_view.py +61 -0
  101. iatoolkit/views/index_view.py +14 -0
  102. iatoolkit/views/init_context_api_view.py +73 -0
  103. iatoolkit/views/llmquery_api_view.py +57 -0
  104. iatoolkit/views/login_simulation_view.py +81 -0
  105. iatoolkit/views/login_view.py +153 -0
  106. iatoolkit/views/logout_api_view.py +49 -0
  107. iatoolkit/views/profile_api_view.py +46 -0
  108. iatoolkit/views/prompt_api_view.py +37 -0
  109. iatoolkit/views/signup_view.py +94 -0
  110. iatoolkit/views/tasks_api_view.py +72 -0
  111. iatoolkit/views/tasks_review_api_view.py +55 -0
  112. iatoolkit/views/user_feedback_api_view.py +60 -0
  113. iatoolkit/views/verify_user_view.py +62 -0
  114. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
  115. iatoolkit-0.66.2.dist-info/RECORD +119 -0
  116. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
  117. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  118. iatoolkit-0.4.2.dist-info/RECORD +0 -32
  119. services/__init__.py +0 -5
  120. services/api_service.py +0 -75
  121. services/user_feedback_service.py +0 -67
  122. services/user_session_context_service.py +0 -85
  123. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,145 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}IAToolkit - Framework de IA{% endblock %}
4
+
5
+ {% block styles %}
6
+ {# Enlazamos la hoja de estilos del website y los iconos de Bootstrap #}
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/landing_page.css') }}">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
9
+ {% endblock %}
10
+
11
+
12
+ {% block content %}
13
+ <div class="container mt-4">
14
+
15
+ <!-- 1. Encabezado con una clase 100% propia y única -->
16
+ <header class="website-header container">
17
+ <span class="website-brand">IAToolkit</span>
18
+ </header>
19
+
20
+ <!-- 2. Sección Principal (Hero) -->
21
+ <section class="hero-section">
22
+ <div class="container">
23
+ <div class="row align-items-center g-5 py-5">
24
+ <div class="col-lg-7">
25
+ <h1 class="hero-title gradient-text">Framework de IA Open Source</h1>
26
+ <ul class="hero-bullets mt-4">
27
+ <li><i class="bi bi-hdd-stack"></i>Integra tus bases de datos SQL y documentos para dar contexto real a tus asistentes.</li>
28
+ <li><i class="bi bi-code-slash"></i>Crea un repositorio de prompts validados por tu equipo para estandarizar y acelerar las tareas de IA.</li>
29
+ <li><i class="bi bi-shield-lock"></i> Monta la plataforma en tu propia infraestructura con control total sobre el LLM, garantizando la privacidad de los datos.</li> </ul>
30
+ </div>
31
+ <div class="col-lg-5">
32
+ <div class="border rounded-3 p-4 p-md-5 shadow-sm bg-light">
33
+ <h3 class="fw-bold text-center mb-3">Prueba la Plataforma</h3>
34
+ <p class="text-muted mb-4">
35
+ Descubre nuestra demo interactiva, configurada con datos de una empresa de muestra. Explora las funcionalidades de la plataforma en un entorno práctico.<br>
36
+ </p>
37
+ <div class="d-grid">
38
+ {# Este botón usa la clase .btn-primary, que es estilizada por .hero-section .btn-primary en el CSS #}
39
+ <a href="{{ url_for('home', company_short_name='sample_company') }}"
40
+ target="_blank"
41
+ rel="noopener noreferrer"
42
+ class="btn btn-primary btn-lg fw-bold">
43
+ Acceder
44
+ </a>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </section>
51
+
52
+ <!-- 3. Sección de Características -->
53
+ <section class="features-section">
54
+ <div class="container">
55
+ <div class="row g-4">
56
+ <div class="col-lg-4 d-flex align-items-stretch">
57
+ <div class="opensource-box">
58
+ <div>
59
+ <div class="opensource-icon"><i class="bi bi-github"></i></div>
60
+ <h3>100% Open Source</h3>
61
+ <p>Construido en Python, IAToolkit te da control total. Audita el código, adáptalo y contribuye a una comunidad en crecimiento.</p>
62
+ </div>
63
+ <a href="https://github.com/flibedinsky/iatoolkit" target="_blank" class="btn btn-light mt-auto">
64
+ <i class="bi bi-github me-2"></i>Ir a GitHub
65
+ {% if iatoolkit_version %}
66
+ <span class="badge bg-dark ms-2">{{ iatoolkit_version }}</span>
67
+ {% endif %}
68
+ </a>
69
+ </div>
70
+ </div>
71
+ <div class="col-lg-4 d-flex align-items-stretch">
72
+ <div class="feature-item">
73
+ <div class="feature-icon"><i class="bi bi-hdd-stack"></i></div>
74
+ <h3>Conecta tus Datos</h3>
75
+ <p>Integra tus bases de datos SQL, documentos (PDFs, TXT) y otros sistemas para que el asistente tenga un contexto real de tu negocio.</p>
76
+ </div>
77
+ </div>
78
+ <div class="col-lg-4 d-flex align-items-stretch">
79
+ <div class="feature-item">
80
+ <div class="feature-icon"><i class="bi bi-gem"></i></div>
81
+ <h3>Multi-LLM</h3>
82
+ <p>No te ates a un solo proveedor. IAToolkit está diseñado para funcionar con diferentes modelos, incluyendo Gemini y OpenAI (GPT).</p>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div class="row g-4 mt-4">
87
+ <div class="col-lg-4 d-flex align-items-stretch">
88
+ <div class="feature-item">
89
+ <div class="feature-icon"><i class="bi bi-magic"></i></div>
90
+ <h3>Prompt Manager</h3>
91
+ <p>Crea, gestiona y comparte una librería de prompts personalizados para tu empresa, optimizando las tareas repetitivas.</p>
92
+ </div>
93
+ </div>
94
+ <div class="col-lg-4 d-flex align-items-stretch">
95
+ <div class="feature-item">
96
+ <div class="feature-icon"><i class="bi bi-palette"></i></div>
97
+ <h3>100% Personalizable</h3>
98
+ <p>Adapta cada aspecto, desde la apariencia visual hasta las capacidades y herramientas del asistente, para que se alinee con la identidad de tu marca.</p>
99
+ </div>
100
+ </div>
101
+ <div class="col-lg-4 d-flex align-items-stretch">
102
+ <div class="feature-item">
103
+ <div class="feature-icon"><i class="bi bi-shield-lock"></i></div>
104
+ <h3>Privacidad Primero</h3>
105
+ <p>Despliega IAToolkit en tus propios servidores. Tus datos y consultas nunca son compartidos con terceros, garantizando la máxima confidencialidad.</p>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </section>
111
+
112
+ <!-- 3.5 Sobre el Autor -->
113
+ <section class="author-section py-5">
114
+ <div class="container">
115
+ <div class="author-card p-4">
116
+ <div class="row align-items-center g-4">
117
+ <div class="col-md-2 text-center">
118
+ <img src="{{ url_for('static', filename='images/fernando.jpeg') }}" alt="Foto de Fernando Libedinsky" class="img-fluid rounded-circle" style="max-width: 120px;">
119
+ </div>
120
+ <div class="col-md-10">
121
+ <div class="d-flex flex-wrap align-items-center mb-2">
122
+ <h5 class="mb-0 me-3">Sobre el autor</h5>
123
+ <a href="https://www.linkedin.com/in/fernandolibedinsky" target="_blank" rel="noopener" class="author-linkedin ms-auto">
124
+ <i class="bi bi-linkedin me-1"></i> LinkedIn
125
+ </a>
126
+ </div>
127
+ <p class="author-bio mb-0">
128
+ Soy <strong>Fernando Libedinsky</strong>, ingeniero de software y creador de <strong>IAToolkit</strong>.
129
+ <br>Tras una extensa trayectoria en el desarrollo de software, sigo movido por la misma curiosidad que me llevó a programar por primera vez: aprender, explorar y construir cosas nuevas.
130
+ <br>IAToolkit es la continuación de ese impulso, una plataforma creada para conectar rapidamente empresas con la IA.
131
+ </p>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </section>
137
+
138
+ <!-- 4. Footer -->
139
+ <footer class="landing-footer">
140
+ <div class="container">
141
+ &copy; 2024 IAToolkit - Proyecto Open Source
142
+ </div>
143
+ </footer>
144
+ </div>
145
+ {% endblock %}
@@ -0,0 +1,34 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Prueba de Login para {{ company_short_name }}{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container-fluid">
7
+ <div class="row flex-fill mt-5 justify-content-center">
8
+ <div class="col-12 col-lg-6">
9
+ <div class="border rounded p-4 p-md-5 shadow-sm bg-light">
10
+ <h3 class="text-muted fw-semibold text-start mb-3">
11
+ Login Externo para <span style="color:#0d6efd;">{{ company_short_name }}</span>
12
+ </h3>
13
+ <div class="text-center mb-4">
14
+ <p class="text-muted widget-intro-text">
15
+ Este formulario simula el inicio de una sesión externa. Al enviar, serás redirigido a la URL de login final.
16
+ </p>
17
+ </div>
18
+
19
+ <!-- Formulario HTML estándar que hace un POST a la misma URL -->
20
+ <form method="POST" action="">
21
+ <div class="mb-3">
22
+ <label for="external_user_id" class="form-label d-block">External user ID</label>
23
+ <input type="text" id="external_user_id" name="external_user_id" class="form-control" required>
24
+ </div>
25
+ <button type="submit" class="btn btn-primary">
26
+ Redirigir a External Login
27
+ </button>
28
+ </form>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ {% endblock %}
34
+
@@ -0,0 +1,104 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Iniciando {{ branding.name | default('IAToolkit') }} 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
+ <div class="ob-nav">
49
+ <button id="prev-card" class="ob-btn btn-branded-primary" aria-label="Anterior">
50
+ <i class="fas fa-chevron-left"></i>
51
+ </button>
52
+ <div id="progress-dots" class="ob-dots"></div>
53
+ <button id="next-card" class="ob-btn btn-branded-primary" aria-label="Siguiente">
54
+ <i class="fas fa-chevron-right"></i>
55
+ </button>
56
+ </div>
57
+ </div>
58
+
59
+ <div id="loading-status" class="ob-loading-band">
60
+ <div class="spinner"></div>
61
+ <p>Inicializando el contexto de {{ branding.name }} para la IA...</p>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <div id="content-container"></div>
67
+ </div>
68
+ {% endblock %}
69
+
70
+ {% block scripts %}
71
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
72
+
73
+ <script src="{{ url_for('static', filename='js/chat_onboarding_button.js', _external=True) }}"></script>
74
+ <script>
75
+ (function() {
76
+ const cardsData = {{ onboarding_cards | tojson }};
77
+ const iframeSrc = "{{ iframe_src_url }}";
78
+
79
+ if (!window.initOnboarding) {
80
+ console.error('initOnboarding no está disponible. Verifica la carga de chat_onboarding.js');
81
+ return;
82
+ }
83
+
84
+ const onboarding = initOnboarding({
85
+ mode: 'shell',
86
+ cards: cardsData,
87
+ ui: {
88
+ icon: '#card-icon',
89
+ title: '#card-title',
90
+ text: '#card-text',
91
+ dots: '#progress-dots',
92
+ prev: '#prev-card',
93
+ next: '#next-card',
94
+ loader: '#loader-wrapper',
95
+ container: '#content-container'
96
+ },
97
+ autoRotateMs: 5000,
98
+ shell: { iframeSrc: iframeSrc }
99
+ });
100
+
101
+ onboarding.start();
102
+ })();
103
+ </script>
104
+ {% endblock %}
@@ -0,0 +1,76 @@
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
+ <!-- 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>
71
+ </div>
72
+ </div>
73
+ </section>
74
+ </div>
75
+
76
+ {% 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,92 @@
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.onboarding_service import OnboardingService
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
+ onboarding_service: OnboardingService,
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.onboarding_service = onboarding_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: Company,
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)
60
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
61
+ company_short_name = company.short_name
62
+
63
+ # this service decides is the context needs to be rebuilt or not
64
+ prep_result = self.query_service.prepare_context(
65
+ company_short_name=company.short_name, user_identifier=user_identifier
66
+ )
67
+
68
+ if prep_result.get('rebuild_needed'):
69
+ # --- SLOW PATH: Render the loading shell ---
70
+ return render_template(
71
+ "onboarding_shell.html",
72
+ iframe_src_url=target_url,
73
+ branding=branding_data,
74
+ onboarding_cards=onboarding_cards
75
+ )
76
+ else:
77
+ # --- FAST PATH: Render the chat page directly ---
78
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
79
+
80
+ # Get the entire 'js_messages' block in the correct language.
81
+ js_translations = self.i18n_service.get_translation_block('js_messages')
82
+
83
+ return render_template(
84
+ "chat.html",
85
+ company_short_name=company_short_name,
86
+ user_identifier=user_identifier,
87
+ prompts=prompts,
88
+ branding=branding_data,
89
+ onboarding_cards=onboarding_cards,
90
+ js_translations=js_translations,
91
+ redeem_token=redeem_token
92
+ )
@@ -0,0 +1,117 @@
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("PASS_RESET_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)
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)
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
+ company=company,
102
+ branding=branding_data,
103
+ form_data={"temp_code": temp_code,
104
+ "new_password": new_password,
105
+ "confirm_password": confirm_password}), 400
106
+
107
+ flash(self.i18n_service.t('flash_messages.password_changed_success'), 'success')
108
+ return redirect(url_for('home', company_short_name=company_short_name))
109
+
110
+ except Exception as e:
111
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
112
+ return render_template(
113
+ "error.html",
114
+ company_short_name=company_short_name,
115
+ branding=branding_data,
116
+ message=message
117
+ ), 500
@@ -0,0 +1,73 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ import os
7
+ import logging
8
+ from flask import request, jsonify, url_for
9
+ from iatoolkit.views.base_login_view import BaseLoginView
10
+
11
+
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
+
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
26
+
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
+ )
38
+
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, user_identifier, target_url, redeem_token)
51
+ except Exception as e:
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