iatoolkit 0.91.1__py3-none-any.whl → 1.4.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 +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 +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +42 -5
- iatoolkit/common/util.py +11 -12
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- 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 +124 -2
- iatoolkit/locales/es.yaml +122 -0
- iatoolkit/repositories/database_manager.py +44 -19
- 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 +88 -39
- iatoolkit/services/configuration_service.py +157 -68
- 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 +412 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +18 -47
- 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 +105 -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 +44 -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/load_company_configuration_api_view.py +49 -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.4.2.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/RECORD +64 -56
- 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.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/iatoolkit/static/js/chat_model_selector.js
|
|
2
|
+
// Gestión del selector de modelo LLM en la barra superior.
|
|
3
|
+
|
|
4
|
+
// Estado global del modelo actual (visible también para otros scripts)
|
|
5
|
+
window.currentLlmModel = window.currentLlmModel || null;
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
8
|
+
/**
|
|
9
|
+
* Lee el modelo guardado en localStorage (si existe y es válido).
|
|
10
|
+
*/
|
|
11
|
+
function loadStoredModelId() {
|
|
12
|
+
try {
|
|
13
|
+
const stored = localStorage.getItem('iatoolkit.selected_llm_model');
|
|
14
|
+
return stored || null;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Guarda el modelo seleccionado en localStorage para esta instancia de navegador.
|
|
22
|
+
* No es crítico: si falla, simplemente no persistimos.
|
|
23
|
+
*/
|
|
24
|
+
function storeModelId(modelId) {
|
|
25
|
+
try {
|
|
26
|
+
if (!modelId) {
|
|
27
|
+
localStorage.removeItem('iatoolkit.selected_llm_model');
|
|
28
|
+
} else {
|
|
29
|
+
localStorage.setItem('iatoolkit.selected_llm_model', modelId);
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// No hacemos nada: fallo silencioso
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Devuelve la lista de modelos disponibles desde la variable global.
|
|
38
|
+
*/
|
|
39
|
+
function getAvailableModels() {
|
|
40
|
+
const raw = window.availableLlmModels;
|
|
41
|
+
if (!Array.isArray(raw)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return raw.map(m => ({
|
|
45
|
+
id: m.id,
|
|
46
|
+
label: m.label || m.id,
|
|
47
|
+
description: m.description || ''
|
|
48
|
+
})).filter(m => !!m.id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Inicializa el estado de currentLlmModel usando SIEMPRE la config de company.yaml:
|
|
53
|
+
* 1) defaultLlmModel (company.yaml)
|
|
54
|
+
* 2) si no existe o no está en la lista, usa el primer modelo disponible.
|
|
55
|
+
*
|
|
56
|
+
* No se lee nada de localStorage en este punto: cada apertura de chat
|
|
57
|
+
* arranca desde la configuración de la compañía.
|
|
58
|
+
*/
|
|
59
|
+
function initCurrentModel() {
|
|
60
|
+
const models = getAvailableModels();
|
|
61
|
+
const defaultId = (window.defaultLlmModel || '').trim() || null;
|
|
62
|
+
|
|
63
|
+
let resolved = null;
|
|
64
|
+
|
|
65
|
+
if (defaultId && models.some(m => m.id === defaultId)) {
|
|
66
|
+
resolved = defaultId;
|
|
67
|
+
} else if (models.length > 0) {
|
|
68
|
+
resolved = models[0].id;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
window.currentLlmModel = resolved;
|
|
72
|
+
return resolved;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pinta la lista de modelos en el popup y marca el seleccionado.
|
|
77
|
+
*/
|
|
78
|
+
function renderModelList() {
|
|
79
|
+
const listEl = document.getElementById('llm-model-list');
|
|
80
|
+
if (!listEl) return;
|
|
81
|
+
|
|
82
|
+
const models = getAvailableModels();
|
|
83
|
+
const activeId = window.currentLlmModel;
|
|
84
|
+
listEl.innerHTML = '';
|
|
85
|
+
|
|
86
|
+
if (!models.length) {
|
|
87
|
+
const emptyItem = document.createElement('div');
|
|
88
|
+
emptyItem.className = 'list-group-item small text-muted';
|
|
89
|
+
emptyItem.textContent = 'No hay modelos configurados.';
|
|
90
|
+
listEl.appendChild(emptyItem);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
models.forEach(model => {
|
|
95
|
+
const item = document.createElement('button');
|
|
96
|
+
item.type = 'button';
|
|
97
|
+
item.className = 'list-group-item list-group-item-action small';
|
|
98
|
+
|
|
99
|
+
const isActive = model.id === activeId;
|
|
100
|
+
if (isActive) {
|
|
101
|
+
item.classList.add('active');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
item.innerHTML = `
|
|
105
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
106
|
+
<div>
|
|
107
|
+
<div class="fw-semibold">${model.label}</div>
|
|
108
|
+
${model.description
|
|
109
|
+
? `<div class="text-muted" style="font-size: 0.8rem;">${model.description}</div>`
|
|
110
|
+
: ''
|
|
111
|
+
}
|
|
112
|
+
</div>
|
|
113
|
+
${isActive ? '<i class="bi bi-check-lg ms-2"></i>' : ''}
|
|
114
|
+
</div>
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
item.addEventListener('click', () => {
|
|
118
|
+
selectModel(model.id);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
listEl.appendChild(item);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Actualiza el label del botón principal con el modelo actual.
|
|
127
|
+
*/
|
|
128
|
+
function updateButtonLabel() {
|
|
129
|
+
const labelEl = document.getElementById('llm-model-button-label');
|
|
130
|
+
if (!labelEl) return;
|
|
131
|
+
|
|
132
|
+
const models = getAvailableModels();
|
|
133
|
+
const activeId = window.currentLlmModel;
|
|
134
|
+
const activeModel = models.find(m => m.id === activeId);
|
|
135
|
+
|
|
136
|
+
if (activeModel) {
|
|
137
|
+
labelEl.textContent = activeModel.label;
|
|
138
|
+
} else if (window.defaultLlmModel) {
|
|
139
|
+
labelEl.textContent = window.defaultLlmModel;
|
|
140
|
+
} else {
|
|
141
|
+
labelEl.textContent = 'Modelo IA';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Selecciona un modelo: actualiza estado global, UI y almacenamiento local.
|
|
147
|
+
*/
|
|
148
|
+
function selectModel(modelId) {
|
|
149
|
+
if (!modelId) return;
|
|
150
|
+
|
|
151
|
+
const models = getAvailableModels();
|
|
152
|
+
const exists = models.some(m => m.id === modelId);
|
|
153
|
+
if (!exists) return;
|
|
154
|
+
|
|
155
|
+
window.currentLlmModel = modelId;
|
|
156
|
+
storeModelId(modelId);
|
|
157
|
+
updateButtonLabel();
|
|
158
|
+
renderModelList();
|
|
159
|
+
hidePopup();
|
|
160
|
+
|
|
161
|
+
if (typeof toastr !== 'undefined') {
|
|
162
|
+
toastr.info(`Modelo actualizado a "${models.find(m => m.id === modelId).label}".`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Muestra/oculta el popup anclado al botón.
|
|
168
|
+
*/
|
|
169
|
+
function togglePopup() {
|
|
170
|
+
const popup = document.getElementById('llm-model-popup');
|
|
171
|
+
const btn = document.getElementById('llm-model-button');
|
|
172
|
+
if (!popup || !btn) return;
|
|
173
|
+
|
|
174
|
+
const isVisible = popup.style.display === 'block';
|
|
175
|
+
|
|
176
|
+
if (isVisible) {
|
|
177
|
+
hidePopup();
|
|
178
|
+
} else {
|
|
179
|
+
const rect = btn.getBoundingClientRect();
|
|
180
|
+
popup.style.display = 'block';
|
|
181
|
+
|
|
182
|
+
// Posicionamos justo debajo del botón, alineado a la izquierda
|
|
183
|
+
popup.style.top = `${rect.bottom + window.scrollY + 4}px`;
|
|
184
|
+
popup.style.left = `${rect.left + window.scrollX}px`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function hidePopup() {
|
|
189
|
+
const popup = document.getElementById('llm-model-popup');
|
|
190
|
+
if (popup) {
|
|
191
|
+
popup.style.display = 'none';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Cierra el popup si el usuario hace click fuera.
|
|
197
|
+
*/
|
|
198
|
+
function setupOutsideClickHandler() {
|
|
199
|
+
document.addEventListener('click', (event) => {
|
|
200
|
+
const popup = document.getElementById('llm-model-popup');
|
|
201
|
+
const btn = document.getElementById('llm-model-button');
|
|
202
|
+
if (!popup || !btn) return;
|
|
203
|
+
|
|
204
|
+
if (popup.style.display !== 'block') return;
|
|
205
|
+
|
|
206
|
+
if (!popup.contains(event.target) && !btn.contains(event.target)) {
|
|
207
|
+
hidePopup();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
213
|
+
// Inicializar estado inicial del modelo
|
|
214
|
+
initCurrentModel();
|
|
215
|
+
updateButtonLabel();
|
|
216
|
+
renderModelList();
|
|
217
|
+
setupOutsideClickHandler();
|
|
218
|
+
|
|
219
|
+
const btn = document.getElementById('llm-model-button');
|
|
220
|
+
if (btn) {
|
|
221
|
+
btn.addEventListener('click', (event) => {
|
|
222
|
+
event.stopPropagation();
|
|
223
|
+
togglePopup();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
})();
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
if (elTitle) elTitle.textContent = c.title || '';
|
|
46
46
|
if (elText) elText.innerHTML = c.text || '';
|
|
47
47
|
if (elExample && c.example) {
|
|
48
|
-
elExample.innerHTML = (
|
|
48
|
+
elExample.innerHTML = ('Example ' + ': ' + c.example) || '';
|
|
49
49
|
}
|
|
50
50
|
else
|
|
51
51
|
elExample.innerHTML = '';
|
|
@@ -18,7 +18,10 @@ $(document).ready(function () {
|
|
|
18
18
|
|
|
19
19
|
// 2. prepare the api parameters
|
|
20
20
|
const apiPath = '/api/init-context';
|
|
21
|
-
const payload = {
|
|
21
|
+
const payload = {
|
|
22
|
+
'user_identifier': window.user_identifier,
|
|
23
|
+
'model': (window.currentLlmModel || window.defaultLlmModel || '')
|
|
24
|
+
};
|
|
22
25
|
|
|
23
26
|
// 3. make the call to callToolkit
|
|
24
27
|
const data = await callToolkit(apiPath, payload, 'POST');
|
|
@@ -499,5 +499,61 @@ li {
|
|
|
499
499
|
cursor: pointer;
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
|
|
503
|
-
|
|
502
|
+
/* Popup de selección de modelo LLM - versión minimalista, sin branding de fondo */
|
|
503
|
+
.llm-model-popup {
|
|
504
|
+
background-color: #f9fafb; /* Fondo claro neutro */
|
|
505
|
+
border-radius: 0.5rem;
|
|
506
|
+
border: 1px solid #e5e7eb; /* Borde gris muy suave */
|
|
507
|
+
color: #111827; /* Texto principal en gris casi negro */
|
|
508
|
+
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12); /* Sombra suave y limpia */
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.llm-model-popup .card-body {
|
|
512
|
+
padding-left: 0.9rem;
|
|
513
|
+
padding-right: 0.9rem;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.llm-model-popup .llm-model-subtitle {
|
|
517
|
+
font-size: 0.8rem;
|
|
518
|
+
color: #6b7280; /* Gris medio para el subtítulo */
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.llm-model-popup .list-group-item {
|
|
522
|
+
border: none;
|
|
523
|
+
padding-top: 0.4rem;
|
|
524
|
+
padding-bottom: 0.4rem;
|
|
525
|
+
background-color: transparent;
|
|
526
|
+
color: inherit;
|
|
527
|
+
border-radius: 0.35rem;
|
|
528
|
+
transition:
|
|
529
|
+
background-color 0.15s ease-in-out,
|
|
530
|
+
transform 0.1s ease-in-out,
|
|
531
|
+
box-shadow 0.15s ease-in-out;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/* Descripción del modelo: mismo color que el nombre, pero un poco más suave */
|
|
535
|
+
.llm-model-popup .list-group-item .text-muted {
|
|
536
|
+
color: currentColor !important;
|
|
537
|
+
opacity: 0.75;
|
|
538
|
+
font-size: 0.8rem;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* Hover más visible, pero manteniendo el estilo minimalista */
|
|
542
|
+
.llm-model-popup .list-group-item:hover {
|
|
543
|
+
background-color: #e5e7eb; /* Gris claro para resaltar bien */
|
|
544
|
+
transform: translateX(2px);
|
|
545
|
+
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.12);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/* Item activo: resalta con el primario, pero fondo muy claro */
|
|
549
|
+
.llm-model-popup .list-group-item.active {
|
|
550
|
+
background-color: #eef2ff; /* Azul/gris muy claro tipo focus */
|
|
551
|
+
color: var(--brand-primary-color, #4C6A8D);
|
|
552
|
+
border: 1px solid var(--brand-primary-color, #4C6A8D);
|
|
553
|
+
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.2);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.llm-model-popup .list-group-item.active .text-muted {
|
|
557
|
+
color: currentColor !important;
|
|
558
|
+
opacity: 0.85;
|
|
559
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* BLOQUE GENERAL */
|
|
2
2
|
.llm-output {
|
|
3
3
|
margin: 0;
|
|
4
|
-
padding:
|
|
4
|
+
padding: 14px;
|
|
5
5
|
font-family: Arial, sans-serif;
|
|
6
6
|
font-size: 16px;
|
|
7
7
|
line-height: 1.5;
|
|
@@ -113,3 +113,36 @@
|
|
|
113
113
|
.nowrap {
|
|
114
114
|
white-space: nowrap;
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/* =========================================================
|
|
118
|
+
REASONING BLOCK (LLM)
|
|
119
|
+
========================================================= */
|
|
120
|
+
|
|
121
|
+
.reasoning-block {
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Botón de mostrar razonamiento */
|
|
125
|
+
.reasoning-toggle {
|
|
126
|
+
font-size: 13px;
|
|
127
|
+
color: #6c757d; /* text-secondary */
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.reasoning-toggle:hover {
|
|
131
|
+
color: #495057;
|
|
132
|
+
text-decoration: underline;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Contenedor del reasoning */
|
|
136
|
+
.reasoning-card {
|
|
137
|
+
background-color: #f8f9fa; /* bg-light */
|
|
138
|
+
border-left: 3px solid #6c757d; /* border-secondary */
|
|
139
|
+
padding: 10px 14px;
|
|
140
|
+
font-size: 14px; /* consistente con llm-output */
|
|
141
|
+
line-height: 1.5;
|
|
142
|
+
color: #555;
|
|
143
|
+
white-space: pre-wrap;
|
|
144
|
+
max-height: 300px;
|
|
145
|
+
overflow-y: auto;
|
|
146
|
+
font-family: inherit; /* 👈 clave: usa la misma fuente base */
|
|
147
|
+
}
|
|
148
|
+
|
|
@@ -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
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
title="Powered by IAToolkit {{ license }} ver. {{ iatoolkit_version }}">
|
|
29
29
|
<i class="bi bi-info-circle" style="color: {{ branding.header_text_color }}; opacity: 0.7; font-size: 0.9rem;"></i>
|
|
30
30
|
</span>
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
</div>
|
|
32
33
|
|
|
33
34
|
<!-- Contenedor para la derecha que agrupa ID de usuario y botones -->
|
|
34
35
|
<div class="d-flex flex-column flex-md-row align-items-center">
|
|
@@ -38,14 +39,39 @@
|
|
|
38
39
|
{{ user_identifier }}
|
|
39
40
|
</span>
|
|
40
41
|
|
|
42
|
+
|
|
41
43
|
<!-- Separador Vertical (Solo visible en Desktop) -->
|
|
42
44
|
<div class="vr mx-3 d-none d-md-block"></div>
|
|
43
45
|
|
|
44
46
|
<!-- Fila 3 (Móvil) / Parte 2 de la Columna Derecha (Desktop): Iconos de Acción -->
|
|
45
47
|
<div class="d-flex align-items-center">
|
|
48
|
+
<!-- Selector de modelo LLM -->
|
|
49
|
+
<button type="button"
|
|
50
|
+
id="llm-model-button"
|
|
51
|
+
class="btn btn-sm py-1 px-3"
|
|
52
|
+
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 }};">
|
|
53
|
+
<i class="bi bi-cpu me-1"></i>
|
|
54
|
+
<span id="llm-model-button-label">
|
|
55
|
+
{{ (llm_available_models[0].label if llm_available_models and llm_available_models[0].label else llm_default_model) or 'Modelo IA' }}
|
|
56
|
+
</span>
|
|
57
|
+
</button>
|
|
58
|
+
<!-- Popup simple para selección de modelo -->
|
|
59
|
+
<div id="llm-model-popup"
|
|
60
|
+
class="card shadow-sm llm-model-popup"
|
|
61
|
+
style="position: absolute; z-index: 9999; display: none; min-width: 260px;">
|
|
62
|
+
<div class="card-body py-2">
|
|
63
|
+
<div class="small mb-1 fw-bold">Modelo de IA</div>
|
|
64
|
+
<div class="small mb-2 llm-model-subtitle">
|
|
65
|
+
Este modelo se usará en las próximas consultas.
|
|
66
|
+
</div>
|
|
67
|
+
<div id="llm-model-list" class="list-group list-group-flush">
|
|
68
|
+
{# El contenido se inyecta vía JavaScript con window.availableLlmModels #}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
46
72
|
<a href="javascript:void(0);"
|
|
47
73
|
id="history-button"
|
|
48
|
-
class="action-icon-style" title="{{ t('ui.tooltips.history') }}"
|
|
74
|
+
class="ms-3 action-icon-style" title="{{ t('ui.tooltips.history') }}"
|
|
49
75
|
style="color: {{ branding.header_text_color }};">
|
|
50
76
|
<i class="bi bi-clock-history"></i>
|
|
51
77
|
</a>
|
|
@@ -77,7 +103,17 @@
|
|
|
77
103
|
title="{{ t('ui.tooltips.usage_guide') }}"
|
|
78
104
|
style="color: {{ branding.header_text_color }};">
|
|
79
105
|
<i class="bi bi-question-circle-fill"></i>
|
|
106
|
+
</a>
|
|
107
|
+
{% if user_role == "admin" and license == 'enterprise' %}
|
|
108
|
+
<a href="/{{ company_short_name }}/admin/dashboard"
|
|
109
|
+
target="_blank"
|
|
110
|
+
id="preferences-button"
|
|
111
|
+
class="ms-3 action-icon-style"
|
|
112
|
+
title="{{ t('ui.tooltips.preferences') }}"
|
|
113
|
+
style="color: {{ branding.header_text_color }};">
|
|
114
|
+
<i class="bi bi-gear"></i>
|
|
80
115
|
</a>
|
|
116
|
+
{% endif %}
|
|
81
117
|
<a href="javascript:void(0);"
|
|
82
118
|
id="logout-button"
|
|
83
119
|
class="ms-3 action-icon-style"
|
|
@@ -220,6 +256,11 @@
|
|
|
220
256
|
window.onboardingCards = {{ onboarding_cards | tojson }};
|
|
221
257
|
window.sendButtonColor = "{{ branding.send_button_color }}";
|
|
222
258
|
|
|
259
|
+
// LLM configuration from backend
|
|
260
|
+
window.defaultLlmModel = {{ llm_default_model | tojson }};
|
|
261
|
+
window.availableLlmModels = {{ llm_available_models | tojson }};
|
|
262
|
+
|
|
263
|
+
|
|
223
264
|
// JS translations helper
|
|
224
265
|
window.i18n = {{ js_translations | tojson }};
|
|
225
266
|
function t_js(key) {
|
|
@@ -238,6 +279,7 @@
|
|
|
238
279
|
<script src="{{ url_for('static', filename='js/chat_logout_button.js', _external=True) }}"></script>
|
|
239
280
|
<script src="{{ url_for('static', filename='js/chat_prompt_manager.js', _external=True) }}"></script>
|
|
240
281
|
<script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
|
|
282
|
+
<script src="{{ url_for('static', filename='js/chat_model_selector.js', _external=True) }}"></script>
|
|
241
283
|
<script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
|
|
242
284
|
|
|
243
285
|
<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,49 @@
|
|
|
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 redirect, url_for, jsonify, request, g
|
|
8
|
+
from injector import inject
|
|
9
|
+
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
class LoadCompanyConfigurationApiView(MethodView):
|
|
15
|
+
@inject
|
|
16
|
+
def __init__(self,
|
|
17
|
+
configuration_service: ConfigurationService,
|
|
18
|
+
profile_service: ProfileService,
|
|
19
|
+
auth_service: AuthService):
|
|
20
|
+
self.configuration_service = configuration_service
|
|
21
|
+
self.profile_service = profile_service
|
|
22
|
+
self.auth_service = auth_service
|
|
23
|
+
|
|
24
|
+
def get(self, company_short_name: str = None):
|
|
25
|
+
try:
|
|
26
|
+
# 1. Get the authenticated user's
|
|
27
|
+
auth_result = self.auth_service.verify(anonymous=True)
|
|
28
|
+
if not auth_result.get("success"):
|
|
29
|
+
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
30
|
+
|
|
31
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
32
|
+
if not company:
|
|
33
|
+
return jsonify({"error": "company not found."}), 404
|
|
34
|
+
|
|
35
|
+
config, errors = self.configuration_service.load_configuration(company_short_name)
|
|
36
|
+
if config:
|
|
37
|
+
self.configuration_service.register_data_sources(company_short_name)
|
|
38
|
+
|
|
39
|
+
# this is fo avoid serialization issues
|
|
40
|
+
if 'company' in config:
|
|
41
|
+
config.pop('company')
|
|
42
|
+
|
|
43
|
+
status_code = 200 if not errors else 400
|
|
44
|
+
return {'config': config, 'errors': [errors]}, status_code
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logging.exception(f"Unexpected error: {e}")
|
|
47
|
+
return {'status': 'error'}, 500
|
|
48
|
+
|
|
49
|
+
|