iatoolkit 1.9.0__py3-none-any.whl → 1.15.3__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 +1 -1
- iatoolkit/common/routes.py +1 -1
- iatoolkit/common/util.py +8 -123
- iatoolkit/core.py +1 -0
- iatoolkit/infra/connectors/file_connector.py +10 -2
- iatoolkit/infra/connectors/google_drive_connector.py +3 -0
- iatoolkit/infra/connectors/local_file_connector.py +3 -0
- iatoolkit/infra/connectors/s3_connector.py +24 -1
- iatoolkit/infra/llm_providers/deepseek_adapter.py +17 -1
- iatoolkit/infra/llm_providers/gemini_adapter.py +117 -18
- iatoolkit/infra/llm_providers/openai_adapter.py +175 -18
- iatoolkit/infra/llm_response.py +13 -0
- iatoolkit/locales/en.yaml +47 -2
- iatoolkit/locales/es.yaml +45 -1
- iatoolkit/repositories/llm_query_repo.py +44 -33
- iatoolkit/services/company_context_service.py +294 -133
- iatoolkit/services/dispatcher_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +26 -4
- iatoolkit/services/llm_client_service.py +58 -2
- iatoolkit/services/prompt_service.py +236 -330
- iatoolkit/services/query_service.py +37 -18
- iatoolkit/services/storage_service.py +92 -0
- iatoolkit/static/js/chat_filepond.js +188 -63
- iatoolkit/static/js/chat_main.js +105 -52
- iatoolkit/static/styles/chat_iatoolkit.css +96 -0
- iatoolkit/system_prompts/query_main.prompt +24 -41
- iatoolkit/templates/chat.html +15 -6
- iatoolkit/views/base_login_view.py +1 -1
- iatoolkit/views/categories_api_view.py +43 -3
- iatoolkit/views/chat_view.py +1 -1
- iatoolkit/views/login_view.py +1 -1
- iatoolkit/views/prompt_api_view.py +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/METADATA +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/RECORD +38 -37
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/WHEEL +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/top_level.txt +0 -0
iatoolkit/static/js/chat_main.js
CHANGED
|
@@ -58,7 +58,6 @@ $(document).ready(function () {
|
|
|
58
58
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
|
|
62
61
|
/**
|
|
63
62
|
* Main function to handle sending a chat message.
|
|
64
63
|
*/
|
|
@@ -93,16 +92,16 @@ const handleChatMessage = async function () {
|
|
|
93
92
|
if (paramsString) { displayMessage += `: ${paramsString}`; }
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
// Simplificado: Si no hay mensaje, el 'finally' se encargará de limpiar.
|
|
97
95
|
if (!displayMessage) {
|
|
98
96
|
return;
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
const files = window.filePond.getFiles();
|
|
100
|
+
|
|
101
|
+
displayUserMessage(displayMessage, isEditable, question, files);
|
|
102
102
|
showSpinner();
|
|
103
103
|
resetAllInputs();
|
|
104
104
|
|
|
105
|
-
const files = window.filePond.getFiles();
|
|
106
105
|
const filesBase64 = await Promise.all(files.map(fileItem => toBase64(fileItem.file)));
|
|
107
106
|
|
|
108
107
|
const data = {
|
|
@@ -112,59 +111,18 @@ const handleChatMessage = async function () {
|
|
|
112
111
|
files: filesBase64.map(f => ({ filename: f.name, content: f.base64 })),
|
|
113
112
|
user_identifier: window.user_identifier,
|
|
114
113
|
model: (window.currentLlmModel || window.defaultLlmModel || '')
|
|
115
|
-
|
|
116
114
|
};
|
|
117
115
|
|
|
118
116
|
const responseData = await callToolkit("/api/llm_query", data, "POST");
|
|
119
|
-
if (responseData && responseData.answer) {
|
|
120
|
-
// CAMBIO: contenedor principal para la respuesta del bot
|
|
121
|
-
const botMessageContainer = $('<div>').addClass('bot-message-container');
|
|
122
|
-
|
|
123
|
-
// 1. Si hay reasoning_content, agregar el acordeón colapsable
|
|
124
|
-
if (responseData.reasoning_content) {
|
|
125
|
-
const uniqueId = 'reasoning-' + Date.now(); // ID único para el collapse
|
|
126
|
-
|
|
127
|
-
const reasoningBlock = $(`
|
|
128
|
-
<div class="reasoning-block">
|
|
129
|
-
<button class="reasoning-toggle btn btn-sm btn-link text-decoration-none p-0"
|
|
130
|
-
type="button"
|
|
131
|
-
data-bs-toggle="collapse"
|
|
132
|
-
data-bs-target="#${uniqueId}"
|
|
133
|
-
aria-expanded="false"
|
|
134
|
-
aria-controls="${uniqueId}">
|
|
135
|
-
<i class="bi bi-lightbulb me-1"></i> ${t_js('show_reasoning')}
|
|
136
|
-
</button>
|
|
137
|
-
|
|
138
|
-
<div class="collapse mt-2" id="${uniqueId}">
|
|
139
|
-
<div class="reasoning-card">
|
|
140
|
-
${responseData.reasoning_content}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
`);
|
|
145
|
-
botMessageContainer.append(reasoningBlock);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 2. Agregar la respuesta final
|
|
149
|
-
const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
|
|
150
|
-
botMessageContainer.append(answerSection);
|
|
151
117
|
|
|
152
|
-
|
|
153
|
-
|
|
118
|
+
// Delegamos el procesamiento de la respuesta a la nueva función
|
|
119
|
+
processBotResponse(responseData);
|
|
154
120
|
|
|
155
|
-
}
|
|
156
121
|
} catch (error) {
|
|
157
122
|
if (error.name === 'AbortError') {
|
|
158
|
-
|
|
159
|
-
// Usando jQuery estándar para construir el elemento ---
|
|
160
|
-
const icon = $('<i>').addClass('bi bi-stop-circle me-2'); // Icono sin "fill" para un look más ligero
|
|
123
|
+
const icon = $('<i>').addClass('bi bi-stop-circle me-2');
|
|
161
124
|
const textSpan = $('<span>').text('La generación de la respuesta ha sido detenida.');
|
|
162
|
-
|
|
163
|
-
const abortMessage = $('<div>')
|
|
164
|
-
.addClass('system-message')
|
|
165
|
-
.append(icon)
|
|
166
|
-
.append(textSpan);
|
|
167
|
-
|
|
125
|
+
const abortMessage = $('<div>').addClass('system-message').append(icon).append(textSpan);
|
|
168
126
|
displayBotMessage(abortMessage);
|
|
169
127
|
} else {
|
|
170
128
|
console.error("Error in handleChatMessage:", error);
|
|
@@ -172,17 +130,78 @@ const handleChatMessage = async function () {
|
|
|
172
130
|
displayBotMessage(errorSection);
|
|
173
131
|
}
|
|
174
132
|
} finally {
|
|
175
|
-
// Este bloque se ejecuta siempre, garantizando que el estado se limpie.
|
|
176
133
|
isRequestInProgress = false;
|
|
177
134
|
hideSpinner();
|
|
178
135
|
toggleSendStopButtons(false);
|
|
179
136
|
updateSendButtonState();
|
|
180
137
|
if (window.filePond) {
|
|
181
|
-
|
|
138
|
+
window.filePond.removeFiles();
|
|
182
139
|
}
|
|
183
140
|
}
|
|
184
141
|
};
|
|
185
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Processes the response data from the LLM and displays it in the chat.
|
|
145
|
+
* Handles multimodal content: uses 'answer' for text (HTML) and 'content_parts' for images.
|
|
146
|
+
* @param {object} responseData - The JSON response from the server.
|
|
147
|
+
*/
|
|
148
|
+
function processBotResponse(responseData) {
|
|
149
|
+
if (!responseData || (!responseData.answer && !responseData.content_parts)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const botMessageContainer = $('<div>').addClass('bot-message-container');
|
|
154
|
+
|
|
155
|
+
// 1. Si hay reasoning_content, agregar el acordeón colapsable
|
|
156
|
+
if (responseData.reasoning_content) {
|
|
157
|
+
const uniqueId = 'reasoning-' + Date.now();
|
|
158
|
+
const reasoningBlock = $(`
|
|
159
|
+
<div class="reasoning-block">
|
|
160
|
+
<button class="reasoning-toggle btn btn-sm btn-link text-decoration-none p-0"
|
|
161
|
+
type="button" data-bs-toggle="collapse" data-bs-target="#${uniqueId}"
|
|
162
|
+
aria-expanded="false" aria-controls="${uniqueId}">
|
|
163
|
+
<i class="bi bi-lightbulb me-1"></i> ${t_js('show_reasoning')}
|
|
164
|
+
</button>
|
|
165
|
+
<div class="collapse mt-2" id="${uniqueId}">
|
|
166
|
+
<div class="reasoning-card">${responseData.reasoning_content}</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
`);
|
|
170
|
+
botMessageContainer.append(reasoningBlock);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 2. Agregar la respuesta final
|
|
174
|
+
const answerSection = $('<div>').addClass('answer-section llm-output');
|
|
175
|
+
|
|
176
|
+
// A. Texto: Usamos 'answer' porque contiene el HTML procesado y limpio del backend.
|
|
177
|
+
// Evitamos usar content_parts[type=text] porque contiene el JSON crudo del LLM.
|
|
178
|
+
if (responseData.answer) {
|
|
179
|
+
answerSection.append(responseData.answer);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// B. Imágenes: Iteramos content_parts buscando SOLO imágenes para adjuntarlas.
|
|
183
|
+
if (responseData.content_parts && responseData.content_parts.length > 0) {
|
|
184
|
+
responseData.content_parts.forEach(part => {
|
|
185
|
+
if (part.type === 'image' && part.source && part.source.url) {
|
|
186
|
+
const imgContainer = $('<div>').addClass('image-part my-3 text-center');
|
|
187
|
+
const img = $('<img>')
|
|
188
|
+
.attr('src', part.source.url)
|
|
189
|
+
.addClass('img-fluid rounded shadow-sm border')
|
|
190
|
+
.css({'max-height': '400px', 'cursor': 'pointer'})
|
|
191
|
+
.on('click', () => window.open(part.source.url, '_blank'));
|
|
192
|
+
|
|
193
|
+
imgContainer.append(img);
|
|
194
|
+
answerSection.append(imgContainer);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
botMessageContainer.append(answerSection);
|
|
200
|
+
|
|
201
|
+
// 3. Mostrar el contenedor completo
|
|
202
|
+
displayBotMessage(botMessageContainer);
|
|
203
|
+
}
|
|
204
|
+
|
|
186
205
|
|
|
187
206
|
/**
|
|
188
207
|
* Resets all inputs to their initial state.
|
|
@@ -316,14 +335,48 @@ const callToolkit = async function(apiPath, data, method, timeoutMs = 500000) {
|
|
|
316
335
|
* @param {string} message - The full message string to display.
|
|
317
336
|
* @param {boolean} isEditable - Determines if the edit icon should be shown.
|
|
318
337
|
* @param {string} originalQuestion - The original text to put back in the textarea for editing.
|
|
338
|
+
* @param {Array} [files] - Optional array of FilePond file items.
|
|
319
339
|
*/
|
|
320
|
-
const displayUserMessage = function(message, isEditable, originalQuestion) {
|
|
340
|
+
const displayUserMessage = function(message, isEditable, originalQuestion, files = []) {
|
|
321
341
|
const chatContainer = $('#chat-container');
|
|
322
342
|
const userMessage = $('<div>').addClass('message shadow-sm');
|
|
323
343
|
const messageText = $('<span>').text(message);
|
|
324
344
|
|
|
325
345
|
userMessage.append(messageText);
|
|
326
346
|
|
|
347
|
+
// Renderizar previsualizaciones de archivos si existen
|
|
348
|
+
if (files && files.length > 0) {
|
|
349
|
+
const attachmentsContainer = $('<div>').addClass('mt-2 d-flex flex-wrap gap-2 ms-3');
|
|
350
|
+
|
|
351
|
+
files.forEach(fileItem => {
|
|
352
|
+
const file = fileItem.file;
|
|
353
|
+
|
|
354
|
+
if (file.type && file.type.startsWith('image/')) {
|
|
355
|
+
// Previsualización de imagen usando URL temporal
|
|
356
|
+
const imgUrl = URL.createObjectURL(file);
|
|
357
|
+
const img = $('<img>')
|
|
358
|
+
.attr('src', imgUrl)
|
|
359
|
+
.addClass('rounded border')
|
|
360
|
+
.css({
|
|
361
|
+
'max-height': '80px',
|
|
362
|
+
'max-width': '120px',
|
|
363
|
+
'object-fit': 'cover',
|
|
364
|
+
'cursor': 'pointer'
|
|
365
|
+
})
|
|
366
|
+
.on('click', () => window.open(imgUrl, '_blank')); // Click para ver en grande
|
|
367
|
+
attachmentsContainer.append(img);
|
|
368
|
+
} else {
|
|
369
|
+
// Icono genérico para documentos
|
|
370
|
+
const badge = $('<span>')
|
|
371
|
+
.addClass('badge bg-light text-dark border p-2')
|
|
372
|
+
.html(`<i class="bi bi-file-earmark-text me-1"></i> ${file.name}`);
|
|
373
|
+
attachmentsContainer.append(badge);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
userMessage.append(attachmentsContainer);
|
|
378
|
+
}
|
|
379
|
+
|
|
327
380
|
if (isEditable) {
|
|
328
381
|
const editIcon = $('<i>').addClass('p-2 bi bi-pencil-fill edit-icon edit-pencil').attr('title', 'Edit query').on('click', function () {
|
|
329
382
|
$('#question').val(originalQuestion)
|
|
@@ -303,6 +303,102 @@ li {
|
|
|
303
303
|
color: #343a40;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
/* --- NUEVO: Dropzone y Lista de Archivos Inline --- */
|
|
307
|
+
|
|
308
|
+
/* Contenedor de la lista de archivos */
|
|
309
|
+
.file-list-inline {
|
|
310
|
+
margin-bottom: 0.5rem;
|
|
311
|
+
display: none; /* Se oculta si no hay archivos */
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.file-list-item {
|
|
315
|
+
background-color: #fff;
|
|
316
|
+
border: 1px solid #dee2e6;
|
|
317
|
+
border-radius: 6px;
|
|
318
|
+
padding: 6px 10px;
|
|
319
|
+
margin-bottom: 5px;
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
font-size: 0.9rem;
|
|
323
|
+
transition: background-color 0.2s;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.file-list-item:hover {
|
|
327
|
+
background-color: #f8f9fa;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.file-list-item i.file-icon {
|
|
331
|
+
font-size: 1.1rem;
|
|
332
|
+
margin-right: 8px;
|
|
333
|
+
color: var(--brand-primary-color);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.file-list-item .file-name {
|
|
337
|
+
flex-grow: 1;
|
|
338
|
+
white-space: nowrap;
|
|
339
|
+
overflow: hidden;
|
|
340
|
+
text-overflow: ellipsis;
|
|
341
|
+
margin-right: 10px;
|
|
342
|
+
color: #495057;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.file-list-item .file-remove {
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
color: #dc3545; /* Rojo Bootstrap estándar */
|
|
348
|
+
opacity: 0.6;
|
|
349
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.file-list-item .file-remove:hover {
|
|
353
|
+
opacity: 1;
|
|
354
|
+
transform: scale(1.1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Dropzone estática */
|
|
358
|
+
.chat-dropzone {
|
|
359
|
+
border: 2px dashed #ced4da;
|
|
360
|
+
border-radius: 0.5rem;
|
|
361
|
+
padding: 10px;
|
|
362
|
+
text-align: center;
|
|
363
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
364
|
+
color: #6c757d;
|
|
365
|
+
cursor: pointer;
|
|
366
|
+
transition: all 0.2s ease-in-out;
|
|
367
|
+
margin-top: 0.5rem;
|
|
368
|
+
font-size: 0.9rem;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.chat-dropzone:hover {
|
|
372
|
+
border-color: var(--brand-primary-color);
|
|
373
|
+
background-color: rgba(255, 255, 255, 0.8);
|
|
374
|
+
color: var(--brand-primary-color);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.chat-dropzone.drag-over {
|
|
378
|
+
border-color: var(--brand-primary-color);
|
|
379
|
+
background-color: #e9ecef;
|
|
380
|
+
transform: scale(1.01);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.chat-dropzone i {
|
|
384
|
+
font-size: 1.2rem;
|
|
385
|
+
vertical-align: middle;
|
|
386
|
+
margin-right: 5px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Ajuste para el contador de archivos */
|
|
390
|
+
.dropzone-counter {
|
|
391
|
+
font-size: 0.8rem;
|
|
392
|
+
margin-left: 5px;
|
|
393
|
+
background: #e9ecef;
|
|
394
|
+
padding: 2px 6px;
|
|
395
|
+
border-radius: 10px;
|
|
396
|
+
color: #495057;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* --- FIN NUEVO --- */
|
|
400
|
+
|
|
401
|
+
|
|
306
402
|
/* Anulación específica para el botón de ENVIAR usando su ID (Máxima Prioridad) */
|
|
307
403
|
#send-button i {
|
|
308
404
|
font-size: 1.7rem; /* Ligeramente más grande */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
Eres un asistente que responde preguntas o ejecuta tareas según el contexto
|
|
1
|
+
Eres un asistente que responde preguntas o ejecuta tareas según el contexto
|
|
2
|
+
de la empresa.
|
|
2
3
|
|
|
3
4
|
### **Nombre de la empresa**
|
|
4
5
|
## Nombre: {{company}}, tambien se conoce como **{{ company_short_name }}**
|
|
@@ -12,9 +13,10 @@ Eres un asistente que responde preguntas o ejecuta tareas según el contexto de
|
|
|
12
13
|
- Rol de usuario: {{ user_rol }}
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
## 🔧 Servicios de datos disponibles en {{
|
|
16
|
+
## 🔧 Servicios de datos disponibles en {{ company_short_name }}
|
|
16
17
|
|
|
17
|
-
A continuación se muestran los *function calls* (tools) que
|
|
18
|
+
A continuación se muestran los *function calls* (tools) que tienes
|
|
19
|
+
disponibles para resolver consultas relacionadas con datos de la empresa.
|
|
18
20
|
Cada servicio incluye su **nombre**, **propósito** y **parámetros esperados**.
|
|
19
21
|
|
|
20
22
|
### 📌 LISTA DE TOOLS DISPONIBLES
|
|
@@ -26,49 +28,30 @@ Cada servicio incluye su **nombre**, **propósito** y **parámetros esperados**.
|
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
1. **NO inventes información** si un tool existe para obtenerla.
|
|
32
|
+
2. Si un usuario solicita datos específicos, **elige el tool cuyo propósito coincida exactamente** con la solicitud.
|
|
33
|
+
3. **Si ningún tool aplica**, responde siguiendo el estilo de un asistente normal.
|
|
30
34
|
|
|
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
|
-
|
|
44
|
-
Eres un asistente que responde preguntas sobre empresas y sus clientes.
|
|
45
35
|
|
|
46
36
|
**Reglas obligatorias de contexto:**
|
|
47
37
|
En caso que te hagan preguntas especificas sobre un cliente, debes asumir que la pregunta se
|
|
48
38
|
refiere al **último cliente identificado** en la conversación.
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
1.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
### **Ejemplo de salida esperada**
|
|
67
|
-
{
|
|
68
|
-
"answer": "El iPhone 15 Pro está disponible por 1099 USD y tu ticket de soporte está en proceso.",
|
|
69
|
-
"aditional_data": {}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
40
|
+
---
|
|
41
|
+
### **Instrucciones de Formato de Salida**
|
|
42
|
+
|
|
43
|
+
1. **Estructura Preferida (JSON):**
|
|
44
|
+
Para la parte **textual** de tu respuesta, utiliza preferentemente el siguiente formato JSON.
|
|
45
|
+
{
|
|
46
|
+
"answer": "Tu respuesta final al usuario. Usa Markdown para formato (negritas, listas, tablas).",
|
|
47
|
+
"aditional_data": {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
2. **Generación de Imágenes y Contenido Multimodal:**
|
|
51
|
+
- Si el usuario te pide generar una imagen y tienes la capacidad nativa **HAZLO**.
|
|
52
|
+
- Genera la imagen como un bloque de contenido separado.
|
|
53
|
+
- **NO** intentes meter la imagen dentro del JSON.
|
|
54
|
+
- En el campo "answer" del JSON, describe o presenta la imagen que has generado.
|
|
55
|
+
- incluye la imagen como parte multimodal (output_image / file / base64) en la respuesta.
|
|
73
56
|
|
|
74
57
|
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -208,12 +208,7 @@
|
|
|
208
208
|
title="{{ t('ui.tooltips.attach_files') }}">
|
|
209
209
|
<i class="bi bi-plus-circle"></i>
|
|
210
210
|
</a>
|
|
211
|
-
|
|
212
|
-
<a class="p-2" href="javascript:void(0);" id="view-files-button"
|
|
213
|
-
title="{{ t('ui.tooltips.view_attached_files') }}">
|
|
214
|
-
<i class="bi bi-file-earmark-text"></i>
|
|
215
|
-
</a>
|
|
216
|
-
</div>
|
|
211
|
+
|
|
217
212
|
</div>
|
|
218
213
|
|
|
219
214
|
<!-- Textarea Central -->
|
|
@@ -238,6 +233,20 @@
|
|
|
238
233
|
</div>
|
|
239
234
|
</div>
|
|
240
235
|
</div>
|
|
236
|
+
|
|
237
|
+
<!-- NUEVO: Área de Adjuntos (Lista + Dropzone) -->
|
|
238
|
+
<div id="attachments-wrapper" class="px-1">
|
|
239
|
+
<!-- Lista de archivos cargados -->
|
|
240
|
+
<div id="inline-file-list" class="file-list-inline"></div>
|
|
241
|
+
|
|
242
|
+
<!-- Dropzone siempre visible -->
|
|
243
|
+
<div id="chat-dropzone" class="chat-dropzone">
|
|
244
|
+
<i class="bi bi-cloud-upload"></i>
|
|
245
|
+
<span>Arrastra archivos aquí o haz clic para adjuntar</span>
|
|
246
|
+
<span id="file-counter" class="dropzone-counter" style="display:none;">0/5</span>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
241
250
|
</div>
|
|
242
251
|
|
|
243
252
|
<!-- Incluir los modales desde un archivo externo -->
|
|
@@ -77,7 +77,7 @@ class BaseLoginView(MethodView):
|
|
|
77
77
|
# LLM configuration: default model and availables
|
|
78
78
|
default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
|
|
79
79
|
|
|
80
|
-
prompts = self.prompt_service.
|
|
80
|
+
prompts = self.prompt_service.get_prompts(company_short_name)
|
|
81
81
|
|
|
82
82
|
# Get the entire 'js_messages' block in the correct language.
|
|
83
83
|
js_translations = self.i18n_service.get_translation_block('js_messages')
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
from flask import jsonify
|
|
6
|
+
from flask import jsonify, request
|
|
7
7
|
from flask.views import MethodView
|
|
8
8
|
from injector import inject
|
|
9
9
|
from iatoolkit.services.auth_service import AuthService
|
|
10
|
+
from iatoolkit.services.prompt_service import PromptService
|
|
10
11
|
from iatoolkit.services.profile_service import ProfileService
|
|
11
12
|
from iatoolkit.services.configuration_service import ConfigurationService
|
|
12
13
|
from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
|
|
@@ -25,12 +26,15 @@ class CategoriesApiView(MethodView):
|
|
|
25
26
|
profile_service: ProfileService,
|
|
26
27
|
configuration_service: ConfigurationService,
|
|
27
28
|
knowledge_base_service: KnowledgeBaseService,
|
|
28
|
-
llm_query_repo: LLMQueryRepo
|
|
29
|
+
llm_query_repo: LLMQueryRepo,
|
|
30
|
+
prompt_service: PromptService):
|
|
29
31
|
self.auth_service = auth_service
|
|
30
32
|
self.profile_service = profile_service
|
|
31
33
|
self.knowledge_base_service = knowledge_base_service
|
|
32
34
|
self.llm_query_repo = llm_query_repo
|
|
33
35
|
self.configuration_service = configuration_service
|
|
36
|
+
self.prompt_service = prompt_service
|
|
37
|
+
|
|
34
38
|
|
|
35
39
|
def get(self, company_short_name):
|
|
36
40
|
try:
|
|
@@ -68,4 +72,40 @@ class CategoriesApiView(MethodView):
|
|
|
68
72
|
|
|
69
73
|
except Exception as e:
|
|
70
74
|
logging.exception(f"Error fetching categories for {company_short_name}: {e}")
|
|
71
|
-
return jsonify({"status": "error", "message": str(e)}), 500
|
|
75
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
76
|
+
|
|
77
|
+
def post(self, company_short_name):
|
|
78
|
+
try:
|
|
79
|
+
# 1. Verify Authentication
|
|
80
|
+
auth_result = self.auth_service.verify()
|
|
81
|
+
if not auth_result.get("success"):
|
|
82
|
+
return jsonify(auth_result), 401
|
|
83
|
+
|
|
84
|
+
# 2. Get Company
|
|
85
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
86
|
+
if not company:
|
|
87
|
+
return jsonify({"error": "Company not found"}), 404
|
|
88
|
+
|
|
89
|
+
# 3. Parse Request
|
|
90
|
+
data = request.get_json() or {}
|
|
91
|
+
|
|
92
|
+
# 4. Sync Collection Types
|
|
93
|
+
# The service expects a list of names strings
|
|
94
|
+
if 'collection_types' in data:
|
|
95
|
+
self.knowledge_base_service.sync_collection_types(
|
|
96
|
+
company_short_name,
|
|
97
|
+
data.get('collection_types', [])
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# 5. Sync Prompt Categories
|
|
101
|
+
if 'prompt_categories' in data:
|
|
102
|
+
self.prompt_service.sync_prompt_categories(
|
|
103
|
+
company_short_name,
|
|
104
|
+
data.get('prompt_categories', [])
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return jsonify({"status": "success", "message": "Categories synchronized successfully"}), 200
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logging.exception(f"Error syncing categories for {company_short_name}: {e}")
|
|
111
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
iatoolkit/views/chat_view.py
CHANGED
|
@@ -48,7 +48,7 @@ class ChatView(MethodView):
|
|
|
48
48
|
branding_data = self.branding_service.get_company_branding(company_short_name)
|
|
49
49
|
onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
|
|
50
50
|
default_llm_model, available_llm_models = self.config_service.get_llm_configuration(company_short_name)
|
|
51
|
-
prompts = self.prompt_service.
|
|
51
|
+
prompts = self.prompt_service.get_prompts(company_short_name)
|
|
52
52
|
js_translations = self.i18n_service.get_translation_block('js_messages')
|
|
53
53
|
|
|
54
54
|
return render_template(
|
iatoolkit/views/login_view.py
CHANGED
|
@@ -142,7 +142,7 @@ class FinalizeContextView(MethodView):
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
# 3. render the chat page.
|
|
145
|
-
prompts = self.prompt_service.
|
|
145
|
+
prompts = self.prompt_service.get_prompts(company_short_name)
|
|
146
146
|
onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
|
|
147
147
|
|
|
148
148
|
# Get the entire 'js_messages' block in the correct language.
|
|
@@ -58,7 +58,7 @@ class PromptApiView(MethodView):
|
|
|
58
58
|
include_all = request.args.get('all', 'false').lower() == 'true'
|
|
59
59
|
|
|
60
60
|
# return prompts based on filter
|
|
61
|
-
return jsonify(self.prompt_service.
|
|
61
|
+
return jsonify(self.prompt_service.get_prompts(company_short_name, include_all=include_all))
|
|
62
62
|
|
|
63
63
|
except Exception as e:
|
|
64
64
|
logging.exception(
|