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.
Files changed (71) hide show
  1. iatoolkit/__init__.py +6 -4
  2. iatoolkit/base_company.py +0 -16
  3. iatoolkit/cli_commands.py +3 -14
  4. iatoolkit/common/exceptions.py +1 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +43 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +47 -5
  10. iatoolkit/common/util.py +32 -13
  11. iatoolkit/company_registry.py +5 -0
  12. iatoolkit/core.py +51 -20
  13. iatoolkit/infra/connectors/file_connector_factory.py +1 -0
  14. iatoolkit/infra/connectors/s3_connector.py +4 -2
  15. iatoolkit/infra/llm_providers/__init__.py +0 -0
  16. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  17. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  18. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  19. iatoolkit/infra/llm_proxy.py +235 -134
  20. iatoolkit/infra/llm_response.py +5 -0
  21. iatoolkit/locales/en.yaml +158 -2
  22. iatoolkit/locales/es.yaml +158 -0
  23. iatoolkit/repositories/database_manager.py +52 -47
  24. iatoolkit/repositories/document_repo.py +7 -0
  25. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  26. iatoolkit/repositories/llm_query_repo.py +2 -0
  27. iatoolkit/repositories/models.py +72 -79
  28. iatoolkit/repositories/profile_repo.py +59 -3
  29. iatoolkit/repositories/vs_repo.py +22 -24
  30. iatoolkit/services/company_context_service.py +126 -53
  31. iatoolkit/services/configuration_service.py +299 -73
  32. iatoolkit/services/dispatcher_service.py +21 -3
  33. iatoolkit/services/file_processor_service.py +0 -5
  34. iatoolkit/services/history_manager_service.py +43 -24
  35. iatoolkit/services/knowledge_base_service.py +425 -0
  36. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
  37. iatoolkit/services/load_documents_service.py +26 -48
  38. iatoolkit/services/profile_service.py +32 -4
  39. iatoolkit/services/prompt_service.py +32 -30
  40. iatoolkit/services/query_service.py +51 -26
  41. iatoolkit/services/sql_service.py +122 -74
  42. iatoolkit/services/tool_service.py +26 -11
  43. iatoolkit/services/user_session_context_service.py +115 -63
  44. iatoolkit/static/js/chat_main.js +44 -4
  45. iatoolkit/static/js/chat_model_selector.js +227 -0
  46. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  47. iatoolkit/static/js/chat_reload_button.js +4 -1
  48. iatoolkit/static/styles/chat_iatoolkit.css +58 -2
  49. iatoolkit/static/styles/llm_output.css +34 -1
  50. iatoolkit/system_prompts/query_main.prompt +26 -2
  51. iatoolkit/templates/base.html +13 -0
  52. iatoolkit/templates/chat.html +45 -2
  53. iatoolkit/templates/onboarding_shell.html +0 -1
  54. iatoolkit/views/base_login_view.py +7 -2
  55. iatoolkit/views/chat_view.py +76 -0
  56. iatoolkit/views/configuration_api_view.py +163 -0
  57. iatoolkit/views/load_document_api_view.py +14 -10
  58. iatoolkit/views/login_view.py +8 -3
  59. iatoolkit/views/rag_api_view.py +216 -0
  60. iatoolkit/views/users_api_view.py +33 -0
  61. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/METADATA +4 -4
  62. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +66 -58
  63. iatoolkit/repositories/tasks_repo.py +0 -52
  64. iatoolkit/services/search_service.py +0 -55
  65. iatoolkit/services/tasks_service.py +0 -188
  66. iatoolkit/views/tasks_api_view.py +0 -72
  67. iatoolkit/views/tasks_review_api_view.py +0 -55
  68. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/WHEEL +0 -0
  69. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
  70. {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
  71. {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 (function calls) disponibles en {{company.name}}:
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
- # servicio {{ loop.index }}: {{ service.name }}
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:**
@@ -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>
@@ -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
- </div>
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
- doc_service: LoadDocumentsService,
20
- profile_repo: ProfileRepo,):
20
+ knowledge_base_service: KnowledgeBaseService,
21
+ profile_repo: ProfileRepo):
21
22
  self.auth_service = auth_service
22
- self.doc_service = doc_service
23
+ self.knowledge_base_service = knowledge_base_service
23
24
  self.profile_repo = profile_repo
24
25
 
25
26
  def post(self):
@@ -48,18 +49,21 @@ class LoadDocumentApiView(MethodView):
48
49
  # get the file content from base64
49
50
  content = base64.b64decode(base64_content)
50
51
 
51
- new_document = self.doc_service._file_processing_callback(
52
+ # Use KnowledgeBaseService for ingestion
53
+ new_document = self.knowledge_base_service.ingest_document_sync(
54
+ company=company,
52
55
  filename=filename,
53
56
  content=content,
54
- company=company,
55
- context={'metadata': metadata})
57
+ metadata=metadata
58
+ )
56
59
 
57
60
  return jsonify({
58
61
  "document_id": new_document.id,
62
+ "status": "active" # ingest_document_sync returns ACTIVE on success
59
63
  }), 200
60
64
 
61
65
  except Exception as e:
62
66
  response = jsonify({"error": str(e)})
63
67
  response.status_code = 500
64
68
 
65
- return response
69
+ return response
@@ -5,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
-