iatoolkit 0.91.1__py3-none-any.whl → 1.7.0__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 +6 -4
- iatoolkit/base_company.py +0 -16
- iatoolkit/cli_commands.py +3 -14
- iatoolkit/common/exceptions.py +1 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +43 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +47 -5
- iatoolkit/common/util.py +32 -13
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- iatoolkit/infra/connectors/file_connector_factory.py +1 -0
- iatoolkit/infra/connectors/s3_connector.py +4 -2
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +158 -2
- iatoolkit/locales/es.yaml +158 -0
- iatoolkit/repositories/database_manager.py +52 -47
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +2 -0
- iatoolkit/repositories/models.py +72 -79
- iatoolkit/repositories/profile_repo.py +59 -3
- iatoolkit/repositories/vs_repo.py +22 -24
- iatoolkit/services/company_context_service.py +126 -53
- iatoolkit/services/configuration_service.py +299 -73
- iatoolkit/services/dispatcher_service.py +21 -3
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +43 -24
- iatoolkit/services/knowledge_base_service.py +425 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +26 -48
- iatoolkit/services/profile_service.py +32 -4
- iatoolkit/services/prompt_service.py +32 -30
- iatoolkit/services/query_service.py +51 -26
- iatoolkit/services/sql_service.py +122 -74
- iatoolkit/services/tool_service.py +26 -11
- iatoolkit/services/user_session_context_service.py +115 -63
- iatoolkit/static/js/chat_main.js +44 -4
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +58 -2
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/query_main.prompt +26 -2
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -2
- iatoolkit/templates/onboarding_shell.html +0 -1
- iatoolkit/views/base_login_view.py +7 -2
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/configuration_api_view.py +163 -0
- iatoolkit/views/load_document_api_view.py +14 -10
- iatoolkit/views/login_view.py +8 -3
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/users_api_view.py +33 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +66 -58
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/top_level.txt +0 -0
|
@@ -12,11 +12,35 @@ Eres un asistente que responde preguntas o ejecuta tareas según el contexto de
|
|
|
12
12
|
- Rol de usuario: {{ user_rol }}
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
## Servicios de datos
|
|
15
|
+
## 🔧 Servicios de datos disponibles en {{ company.name }}
|
|
16
|
+
|
|
17
|
+
A continuación se muestran los *function calls* (tools) que puedes usar para resolver tareas relacionadas con datos.
|
|
18
|
+
Cada servicio incluye su **nombre**, **propósito** y **parámetros esperados**.
|
|
19
|
+
|
|
20
|
+
### 📌 LISTA DE TOOLS DISPONIBLES
|
|
16
21
|
{% for service in service_list %}
|
|
17
|
-
|
|
22
|
+
#### Tool {{ loop.index }}: `{{ service.name }}`
|
|
23
|
+
- **Descripción:** {{ service.description }}
|
|
24
|
+
- **Parámetros:** {{ service.parameters | tojson }}
|
|
18
25
|
{% endfor %}
|
|
19
26
|
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### ⚠️ REGLAS IMPORTANTES PARA EL USO DE TOOLS
|
|
30
|
+
|
|
31
|
+
1. **Debes usar un tool cuando la tarea lo requiera.**
|
|
32
|
+
Ejemplos:
|
|
33
|
+
- Consultar bases de datos
|
|
34
|
+
- Obtener información corporativa
|
|
35
|
+
- Ejecutar SQL
|
|
36
|
+
- Cargar documentos
|
|
37
|
+
|
|
38
|
+
2. **NO inventes información** si un tool existe para obtenerla.
|
|
39
|
+
3. Si un usuario solicita datos específicos, **elige el tool cuyo propósito coincida exactamente** con la solicitud.
|
|
40
|
+
4. **Si ningún tool aplica**, responde siguiendo el estilo de un asistente normal.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
20
44
|
Eres un asistente que responde preguntas sobre empresas y sus clientes.
|
|
21
45
|
|
|
22
46
|
**Reglas obligatorias de contexto:**
|
iatoolkit/templates/base.html
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
+
{% if google_analytics_id %}
|
|
5
|
+
<!-- Google tag (gtag.js) -->
|
|
6
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-96G1XCM2CC"></script>
|
|
7
|
+
<script>
|
|
8
|
+
window.dataLayer = window.dataLayer || [];
|
|
9
|
+
function gtag(){dataLayer.push(arguments);}
|
|
10
|
+
gtag('js', new Date());
|
|
11
|
+
|
|
12
|
+
gtag('config', '{{ google_analytics_id }}');
|
|
13
|
+
</script>
|
|
14
|
+
{% endif %}
|
|
15
|
+
|
|
16
|
+
|
|
4
17
|
<meta charset="UTF-8">
|
|
5
18
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
19
|
<title>{% block title %}Chatbot{% endblock %}</title>
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
{# Movemos los estilos y los links aquí para que se rendericen en el <head> #}
|
|
9
9
|
<style>
|
|
10
10
|
{{ branding.css_variables | safe }}
|
|
11
|
+
{{ branding.css_variables | safe }}
|
|
11
12
|
</style>
|
|
12
13
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
|
13
14
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
title="Powered by IAToolkit {{ license }} ver. {{ iatoolkit_version }}">
|
|
29
30
|
<i class="bi bi-info-circle" style="color: {{ branding.header_text_color }}; opacity: 0.7; font-size: 0.9rem;"></i>
|
|
30
31
|
</span>
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
</div>
|
|
32
34
|
|
|
33
35
|
<!-- Contenedor para la derecha que agrupa ID de usuario y botones -->
|
|
34
36
|
<div class="d-flex flex-column flex-md-row align-items-center">
|
|
@@ -38,14 +40,39 @@
|
|
|
38
40
|
{{ user_identifier }}
|
|
39
41
|
</span>
|
|
40
42
|
|
|
43
|
+
|
|
41
44
|
<!-- Separador Vertical (Solo visible en Desktop) -->
|
|
42
45
|
<div class="vr mx-3 d-none d-md-block"></div>
|
|
43
46
|
|
|
44
47
|
<!-- Fila 3 (Móvil) / Parte 2 de la Columna Derecha (Desktop): Iconos de Acción -->
|
|
45
48
|
<div class="d-flex align-items-center">
|
|
49
|
+
<!-- Selector de modelo LLM -->
|
|
50
|
+
<button type="button"
|
|
51
|
+
id="llm-model-button"
|
|
52
|
+
class="btn btn-sm py-1 px-3"
|
|
53
|
+
style="border-radius: 0.4rem; border: 1px solid rgba(255,255,255,0.7); background: rgba(0,0,0,0.08); color: {{ branding.header_text_color }};">
|
|
54
|
+
<i class="bi bi-cpu me-1"></i>
|
|
55
|
+
<span id="llm-model-button-label">
|
|
56
|
+
{{ (llm_available_models[0].label if llm_available_models and llm_available_models[0].label else llm_default_model) or 'Modelo IA' }}
|
|
57
|
+
</span>
|
|
58
|
+
</button>
|
|
59
|
+
<!-- Popup simple para selección de modelo -->
|
|
60
|
+
<div id="llm-model-popup"
|
|
61
|
+
class="card shadow-sm llm-model-popup"
|
|
62
|
+
style="position: absolute; z-index: 9999; display: none; min-width: 260px;">
|
|
63
|
+
<div class="card-body py-2">
|
|
64
|
+
<div class="small mb-1 fw-bold">Modelo de IA</div>
|
|
65
|
+
<div class="small mb-2 llm-model-subtitle">
|
|
66
|
+
Este modelo se usará en las próximas consultas.
|
|
67
|
+
</div>
|
|
68
|
+
<div id="llm-model-list" class="list-group list-group-flush">
|
|
69
|
+
{# El contenido se inyecta vía JavaScript con window.availableLlmModels #}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
46
73
|
<a href="javascript:void(0);"
|
|
47
74
|
id="history-button"
|
|
48
|
-
class="action-icon-style" title="{{ t('ui.tooltips.history') }}"
|
|
75
|
+
class="ms-3 action-icon-style" title="{{ t('ui.tooltips.history') }}"
|
|
49
76
|
style="color: {{ branding.header_text_color }};">
|
|
50
77
|
<i class="bi bi-clock-history"></i>
|
|
51
78
|
</a>
|
|
@@ -77,7 +104,17 @@
|
|
|
77
104
|
title="{{ t('ui.tooltips.usage_guide') }}"
|
|
78
105
|
style="color: {{ branding.header_text_color }};">
|
|
79
106
|
<i class="bi bi-question-circle-fill"></i>
|
|
107
|
+
</a>
|
|
108
|
+
{% if user_role != "user" and license == 'enterprise' %}
|
|
109
|
+
<a href="/{{ company_short_name }}/admin/dashboard"
|
|
110
|
+
target="_blank"
|
|
111
|
+
id="preferences-button"
|
|
112
|
+
class="ms-3 action-icon-style"
|
|
113
|
+
title="{{ t('ui.tooltips.preferences') }}"
|
|
114
|
+
style="color: {{ branding.header_text_color }};">
|
|
115
|
+
<i class="bi bi-gear"></i>
|
|
80
116
|
</a>
|
|
117
|
+
{% endif %}
|
|
81
118
|
<a href="javascript:void(0);"
|
|
82
119
|
id="logout-button"
|
|
83
120
|
class="ms-3 action-icon-style"
|
|
@@ -220,6 +257,11 @@
|
|
|
220
257
|
window.onboardingCards = {{ onboarding_cards | tojson }};
|
|
221
258
|
window.sendButtonColor = "{{ branding.send_button_color }}";
|
|
222
259
|
|
|
260
|
+
// LLM configuration from backend
|
|
261
|
+
window.defaultLlmModel = {{ llm_default_model | tojson }};
|
|
262
|
+
window.availableLlmModels = {{ llm_available_models | tojson }};
|
|
263
|
+
|
|
264
|
+
|
|
223
265
|
// JS translations helper
|
|
224
266
|
window.i18n = {{ js_translations | tojson }};
|
|
225
267
|
function t_js(key) {
|
|
@@ -238,6 +280,7 @@
|
|
|
238
280
|
<script src="{{ url_for('static', filename='js/chat_logout_button.js', _external=True) }}"></script>
|
|
239
281
|
<script src="{{ url_for('static', filename='js/chat_prompt_manager.js', _external=True) }}"></script>
|
|
240
282
|
<script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
|
|
283
|
+
<script src="{{ url_for('static', filename='js/chat_model_selector.js', _external=True) }}"></script>
|
|
241
284
|
<script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
|
|
242
285
|
|
|
243
286
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
<div class="ob-stack">
|
|
39
39
|
<h1 id="ob-brand-header" class="ob-brand-header">
|
|
40
40
|
<span class="brand-name text-brand-primary">{{ branding.name | default('IAToolkit') }}</span>
|
|
41
|
-
<span class="brand-rest"> IA</span>
|
|
42
41
|
</h1>
|
|
43
42
|
|
|
44
43
|
<div id="card-container" class="ob-card">
|
|
@@ -74,6 +74,9 @@ class BaseLoginView(MethodView):
|
|
|
74
74
|
)
|
|
75
75
|
else:
|
|
76
76
|
# --- FAST PATH: Render the chat page directly ---
|
|
77
|
+
# LLM configuration: default model and availables
|
|
78
|
+
default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
|
|
79
|
+
|
|
77
80
|
prompts = self.prompt_service.get_user_prompts(company_short_name)
|
|
78
81
|
|
|
79
82
|
# Get the entire 'js_messages' block in the correct language.
|
|
@@ -87,5 +90,7 @@ class BaseLoginView(MethodView):
|
|
|
87
90
|
branding=branding_data,
|
|
88
91
|
onboarding_cards=onboarding_cards,
|
|
89
92
|
js_translations=js_translations,
|
|
90
|
-
redeem_token=redeem_token
|
|
91
|
-
|
|
93
|
+
redeem_token=redeem_token,
|
|
94
|
+
llm_default_model=default_llm_model,
|
|
95
|
+
llm_available_models = available_llm_models,
|
|
96
|
+
)
|
|
@@ -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,163 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import jsonify, request
|
|
7
|
+
from flask.views import MethodView
|
|
8
|
+
from injector import inject
|
|
9
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.auth_service import AuthService
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigurationApiView(MethodView):
|
|
16
|
+
"""
|
|
17
|
+
API View to manage company configuration.
|
|
18
|
+
Supports loading, updating specific keys, and validating the configuration.
|
|
19
|
+
"""
|
|
20
|
+
@inject
|
|
21
|
+
def __init__(self,
|
|
22
|
+
configuration_service: ConfigurationService,
|
|
23
|
+
profile_service: ProfileService,
|
|
24
|
+
auth_service: AuthService):
|
|
25
|
+
self.configuration_service = configuration_service
|
|
26
|
+
self.profile_service = profile_service
|
|
27
|
+
self.auth_service = auth_service
|
|
28
|
+
|
|
29
|
+
def get(self, company_short_name: str = None):
|
|
30
|
+
"""
|
|
31
|
+
Loads the current configuration for the company.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# 1. Verify authentication
|
|
35
|
+
auth_result = self.auth_service.verify(anonymous=True)
|
|
36
|
+
if not auth_result.get("success"):
|
|
37
|
+
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
38
|
+
|
|
39
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
40
|
+
if not company:
|
|
41
|
+
return jsonify({"error": "company not found."}), 404
|
|
42
|
+
|
|
43
|
+
config, errors = self.configuration_service.load_configuration(company_short_name)
|
|
44
|
+
|
|
45
|
+
# Register data sources to ensure services are up to date with loaded config
|
|
46
|
+
if config:
|
|
47
|
+
self.configuration_service.register_data_sources(company_short_name)
|
|
48
|
+
|
|
49
|
+
# Remove non-serializable objects
|
|
50
|
+
if 'company' in config:
|
|
51
|
+
config.pop('company')
|
|
52
|
+
|
|
53
|
+
status_code = 200 if not errors else 400
|
|
54
|
+
return jsonify({'config': config, 'errors': errors}), status_code
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logging.exception(f"Unexpected error loading config: {e}")
|
|
57
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
58
|
+
|
|
59
|
+
def patch(self, company_short_name: str):
|
|
60
|
+
"""
|
|
61
|
+
Updates a specific configuration key.
|
|
62
|
+
Body: { "key": "llm.model", "value": "gpt-4" }
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
auth_result = self.auth_service.verify(anonymous=False) # Require valid user for updates
|
|
66
|
+
if not auth_result.get("success"):
|
|
67
|
+
return jsonify(auth_result), 401
|
|
68
|
+
|
|
69
|
+
payload = request.get_json()
|
|
70
|
+
key = payload.get('key')
|
|
71
|
+
value = payload.get('value')
|
|
72
|
+
|
|
73
|
+
if not key:
|
|
74
|
+
return jsonify({'error': 'Missing "key" in payload'}), 400
|
|
75
|
+
|
|
76
|
+
logging.info(f"Updating config key '{key}' for company '{company_short_name}'")
|
|
77
|
+
|
|
78
|
+
updated_config, errors = self.configuration_service.update_configuration_key(
|
|
79
|
+
company_short_name, key, value
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Remove non-serializable objects
|
|
83
|
+
if 'company' in updated_config:
|
|
84
|
+
updated_config.pop('company')
|
|
85
|
+
|
|
86
|
+
if errors:
|
|
87
|
+
return jsonify({'status': 'invalid', 'errors': errors, 'config': updated_config}), 400
|
|
88
|
+
|
|
89
|
+
return jsonify({'status': 'success', 'config': updated_config}), 200
|
|
90
|
+
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
return jsonify({'error': 'Configuration file not found'}), 404
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logging.exception(f"Error updating config: {e}")
|
|
95
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
96
|
+
|
|
97
|
+
def post(self, company_short_name: str):
|
|
98
|
+
"""
|
|
99
|
+
Adds a new configuration key.
|
|
100
|
+
Body: { "parent_key": "llm", "key": "max_tokens", "value": 2048 }
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
auth_result = self.auth_service.verify(anonymous=False)
|
|
104
|
+
if not auth_result.get("success"):
|
|
105
|
+
return jsonify(auth_result), 401
|
|
106
|
+
|
|
107
|
+
payload = request.get_json()
|
|
108
|
+
parent_key = payload.get('parent_key', '') # Optional, defaults to root
|
|
109
|
+
key = payload.get('key')
|
|
110
|
+
value = payload.get('value')
|
|
111
|
+
|
|
112
|
+
if not key:
|
|
113
|
+
return jsonify({'error': 'Missing "key" in payload'}), 400
|
|
114
|
+
|
|
115
|
+
logging.info(f"Adding config key '{key}' under '{parent_key}' for company '{company_short_name}'")
|
|
116
|
+
|
|
117
|
+
updated_config, errors = self.configuration_service.add_configuration_key(
|
|
118
|
+
company_short_name, parent_key, key, value
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Remove non-serializable objects
|
|
122
|
+
if 'company' in updated_config:
|
|
123
|
+
updated_config.pop('company')
|
|
124
|
+
|
|
125
|
+
if errors:
|
|
126
|
+
return jsonify({'status': 'invalid', 'errors': errors, 'config': updated_config}), 400
|
|
127
|
+
|
|
128
|
+
return jsonify({'status': 'success', 'config': updated_config}), 200
|
|
129
|
+
|
|
130
|
+
except FileNotFoundError:
|
|
131
|
+
return jsonify({'error': 'Configuration file not found'}), 404
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logging.exception(f"Error adding config key: {e}")
|
|
134
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
135
|
+
|
|
136
|
+
class ValidateConfigurationApiView(MethodView):
|
|
137
|
+
"""
|
|
138
|
+
API View to trigger an explicit validation of the current configuration.
|
|
139
|
+
Useful for UI to check status without modifying data.
|
|
140
|
+
"""
|
|
141
|
+
@inject
|
|
142
|
+
def __init__(self,
|
|
143
|
+
configuration_service: ConfigurationService,
|
|
144
|
+
auth_service: AuthService):
|
|
145
|
+
self.configuration_service = configuration_service
|
|
146
|
+
self.auth_service = auth_service
|
|
147
|
+
|
|
148
|
+
def get(self, company_short_name: str):
|
|
149
|
+
try:
|
|
150
|
+
auth_result = self.auth_service.verify(anonymous=False)
|
|
151
|
+
if not auth_result.get("success"):
|
|
152
|
+
return jsonify(auth_result), 401
|
|
153
|
+
|
|
154
|
+
errors = self.configuration_service.validate_configuration(company_short_name)
|
|
155
|
+
|
|
156
|
+
if errors:
|
|
157
|
+
return jsonify({'status': 'invalid', 'errors': errors}), 200 # 200 OK because check succeeded
|
|
158
|
+
|
|
159
|
+
return jsonify({'status': 'valid', 'errors': []}), 200
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logging.exception(f"Error validating config: {e}")
|
|
163
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
@@ -5,21 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
7
|
from flask import request, jsonify
|
|
8
|
-
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
9
|
-
from iatoolkit.services.auth_service import AuthService
|
|
10
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
11
8
|
from injector import inject
|
|
12
9
|
import base64
|
|
13
10
|
|
|
11
|
+
from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
|
|
12
|
+
from iatoolkit.services.auth_service import AuthService
|
|
13
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
class LoadDocumentApiView(MethodView):
|
|
16
17
|
@inject
|
|
17
18
|
def __init__(self,
|
|
18
19
|
auth_service: AuthService,
|
|
19
|
-
|
|
20
|
-
profile_repo: ProfileRepo
|
|
20
|
+
knowledge_base_service: KnowledgeBaseService,
|
|
21
|
+
profile_repo: ProfileRepo):
|
|
21
22
|
self.auth_service = auth_service
|
|
22
|
-
self.
|
|
23
|
+
self.knowledge_base_service = knowledge_base_service
|
|
23
24
|
self.profile_repo = profile_repo
|
|
24
25
|
|
|
25
26
|
def post(self):
|
|
@@ -48,18 +49,21 @@ class LoadDocumentApiView(MethodView):
|
|
|
48
49
|
# get the file content from base64
|
|
49
50
|
content = base64.b64decode(base64_content)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
# Use KnowledgeBaseService for ingestion
|
|
53
|
+
new_document = self.knowledge_base_service.ingest_document_sync(
|
|
54
|
+
company=company,
|
|
52
55
|
filename=filename,
|
|
53
56
|
content=content,
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
metadata=metadata
|
|
58
|
+
)
|
|
56
59
|
|
|
57
60
|
return jsonify({
|
|
58
61
|
"document_id": new_document.id,
|
|
62
|
+
"status": "active" # ingest_document_sync returns ACTIVE on success
|
|
59
63
|
}), 200
|
|
60
64
|
|
|
61
65
|
except Exception as e:
|
|
62
66
|
response = jsonify({"error": str(e)})
|
|
63
67
|
response.status_code = 500
|
|
64
68
|
|
|
65
|
-
return response
|
|
69
|
+
return response
|
iatoolkit/views/login_view.py
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
from flask.views import MethodView
|
|
7
7
|
from flask import (request, redirect, render_template, url_for,
|
|
8
|
-
render_template_string, flash)
|
|
8
|
+
render_template_string, flash, make_response)
|
|
9
9
|
from injector import inject
|
|
10
10
|
from iatoolkit.services.profile_service import ProfileService
|
|
11
11
|
from iatoolkit.services.jwt_service import JWTService
|
|
@@ -133,6 +133,8 @@ class FinalizeContextView(MethodView):
|
|
|
133
133
|
message="Empresa no encontrada"), 404
|
|
134
134
|
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
135
135
|
|
|
136
|
+
default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
|
|
137
|
+
|
|
136
138
|
# 2. Finalize the context rebuild (the heavy task).
|
|
137
139
|
self.query_service.set_context_for_llm(
|
|
138
140
|
company_short_name=company_short_name,
|
|
@@ -146,6 +148,8 @@ class FinalizeContextView(MethodView):
|
|
|
146
148
|
# Get the entire 'js_messages' block in the correct language.
|
|
147
149
|
js_translations = self.i18n_service.get_translation_block('js_messages')
|
|
148
150
|
|
|
151
|
+
# Importante: no envolver con make_response; dejar que Flask gestione
|
|
152
|
+
# tanto strings como tuplas (string, status) que pueda devolver render_template
|
|
149
153
|
return render_template(
|
|
150
154
|
"chat.html",
|
|
151
155
|
company_short_name=company_short_name,
|
|
@@ -154,7 +158,9 @@ class FinalizeContextView(MethodView):
|
|
|
154
158
|
prompts=prompts,
|
|
155
159
|
onboarding_cards=onboarding_cards,
|
|
156
160
|
js_translations=js_translations,
|
|
157
|
-
redeem_token=token
|
|
161
|
+
redeem_token=token,
|
|
162
|
+
llm_default_model=default_llm_model,
|
|
163
|
+
llm_available_models=available_llm_models,
|
|
158
164
|
)
|
|
159
165
|
|
|
160
166
|
except Exception as e:
|
|
@@ -162,4 +168,3 @@ class FinalizeContextView(MethodView):
|
|
|
162
168
|
company_short_name=company_short_name,
|
|
163
169
|
branding=branding_data,
|
|
164
170
|
message=f"An unexpected error occurred during context loading: {str(e)}"), 500
|
|
165
|
-
|