salomonsky commited on
Commit
2f5df92
·
verified ·
1 Parent(s): b0820d1

Upload 9 files

Browse files
static/css/.gitkeep ADDED
File without changes
static/css/style.css ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Estilos generales */
2
+ body {
3
+ background-color: #f5f5f5;
4
+ font-family: Arial, sans-serif;
5
+ }
6
+
7
+ .container {
8
+ max-width: 1200px;
9
+ margin: 0 auto;
10
+ }
11
+
12
+ /* Estilos de la tarjeta */
13
+ .card {
14
+ border-radius: 15px;
15
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
16
+ }
17
+
18
+ .card-header {
19
+ border-radius: 15px 15px 0 0;
20
+ padding: 1rem;
21
+ }
22
+
23
+ /* Estilos de las pestañas */
24
+ .nav-tabs .nav-link {
25
+ border: none;
26
+ color: #fff;
27
+ padding: 0.5rem 1rem;
28
+ margin-right: 0.5rem;
29
+ border-radius: 5px;
30
+ transition: background-color 0.3s;
31
+ }
32
+
33
+ .nav-tabs .nav-link:hover {
34
+ background-color: rgba(255, 255, 255, 0.1);
35
+ }
36
+
37
+ .nav-tabs .nav-link.active {
38
+ background-color: rgba(255, 255, 255, 0.2);
39
+ border: none;
40
+ }
41
+
42
+ /* Estilos del chat */
43
+ .chat-box {
44
+ height: 400px;
45
+ overflow-y: auto;
46
+ padding: 1rem;
47
+ background-color: #fff;
48
+ border-radius: 5px;
49
+ margin-bottom: 1rem;
50
+ }
51
+
52
+ .message {
53
+ padding: 0.5rem 1rem;
54
+ margin-bottom: 0.5rem;
55
+ border-radius: 10px;
56
+ max-width: 80%;
57
+ }
58
+
59
+ .user-message {
60
+ background-color: #e3f2fd;
61
+ margin-left: auto;
62
+ }
63
+
64
+ .bot-message {
65
+ background-color: #f5f5f5;
66
+ margin-right: auto;
67
+ }
68
+
69
+ /* Estilos de los controles */
70
+ .input-group {
71
+ margin-top: 1rem;
72
+ }
73
+
74
+ .btn {
75
+ padding: 0.5rem 1rem;
76
+ border-radius: 5px;
77
+ transition: all 0.3s;
78
+ }
79
+
80
+ .btn i {
81
+ margin-right: 0.5rem;
82
+ }
83
+
84
+ .btn-primary {
85
+ background-color: #2196f3;
86
+ border-color: #2196f3;
87
+ }
88
+
89
+ .btn-primary:hover {
90
+ background-color: #1976d2;
91
+ border-color: #1976d2;
92
+ }
93
+
94
+ .btn-success {
95
+ background-color: #4caf50;
96
+ border-color: #4caf50;
97
+ }
98
+
99
+ .btn-success:hover {
100
+ background-color: #388e3c;
101
+ border-color: #388e3c;
102
+ }
103
+
104
+ /* Estilos para el botón de grabación */
105
+ #startRecording {
106
+ position: relative;
107
+ transition: all 0.3s;
108
+ }
109
+
110
+ #startRecording.active {
111
+ background-color: #f44336;
112
+ border-color: #f44336;
113
+ }
114
+
115
+ #startRecording.active:hover {
116
+ background-color: #d32f2f;
117
+ border-color: #d32f2f;
118
+ }
119
+
120
+ /* Estilos para los selectores */
121
+ .form-select {
122
+ padding: 0.5rem;
123
+ border-radius: 5px;
124
+ border: 1px solid #ddd;
125
+ background-color: #fff;
126
+ }
127
+
128
+ /* Estilos para la configuración */
129
+ .form-label {
130
+ font-weight: bold;
131
+ color: #333;
132
+ }
133
+
134
+ .form-control {
135
+ border-radius: 5px;
136
+ border: 1px solid #ddd;
137
+ padding: 0.5rem;
138
+ }
139
+
140
+ /* Estilos para el estado */
141
+ #statusLabel {
142
+ font-size: 0.9rem;
143
+ color: #666;
144
+ margin-bottom: 1rem;
145
+ text-align: center;
146
+ }
147
+
148
+ /* Animaciones */
149
+ @keyframes pulse {
150
+ 0% {
151
+ transform: scale(1);
152
+ }
153
+ 50% {
154
+ transform: scale(1.05);
155
+ }
156
+ 100% {
157
+ transform: scale(1);
158
+ }
159
+ }
160
+
161
+ .recording {
162
+ animation: pulse 1.5s infinite;
163
+ }
164
+
165
+ /* Responsive */
166
+ @media (max-width: 768px) {
167
+ .container {
168
+ padding: 0.5rem;
169
+ }
170
+
171
+ .message {
172
+ max-width: 90%;
173
+ }
174
+
175
+ .chat-box {
176
+ height: 300px;
177
+ }
178
+ }
static/css/styles.css ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .chat-container {
2
+ max-width: 800px;
3
+ margin: 20px auto;
4
+ padding: 20px;
5
+ border: 1px solid #ccc;
6
+ border-radius: 5px;
7
+ }
8
+
9
+ .chat-messages {
10
+ height: 400px;
11
+ overflow-y: auto;
12
+ padding: 10px;
13
+ margin-bottom: 20px;
14
+ border: 1px solid #eee;
15
+ }
16
+
17
+ .message {
18
+ margin: 10px 0;
19
+ padding: 10px;
20
+ border-radius: 5px;
21
+ }
22
+
23
+ .user-message {
24
+ background-color: #e3f2fd;
25
+ margin-left: 20%;
26
+ }
27
+
28
+ .bot-message {
29
+ background-color: #f5f5f5;
30
+ margin-right: 20%;
31
+ }
32
+
33
+ .chat-input {
34
+ display: flex;
35
+ gap: 10px;
36
+ }
37
+
38
+ #user-input {
39
+ flex: 1;
40
+ padding: 10px;
41
+ border: 1px solid #ccc;
42
+ border-radius: 5px;
43
+ }
44
+
45
+ #send-button {
46
+ padding: 10px 20px;
47
+ background-color: #2196f3;
48
+ color: white;
49
+ border: none;
50
+ border-radius: 5px;
51
+ cursor: pointer;
52
+ }
53
+
54
+ #send-button:hover {
55
+ background-color: #1976d2;
56
+ }
static/js/.gitkeep ADDED
File without changes
static/js/chat.js ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Variables globales
2
+ let currentMode = 'seguros';
3
+ let currentModel = 'Gemini 8b';
4
+ let currentTTS = 'EDGE';
5
+ let isListening = false;
6
+ let recognition = null;
7
+
8
+ function playAudio(button, text) {
9
+ const icon = button ? button.querySelector('i') : null;
10
+ text = text || (button ? button.getAttribute('data-text') : '');
11
+
12
+ if (!text) {
13
+ console.error('No text provided for audio');
14
+ return;
15
+ }
16
+
17
+ if (button) {
18
+ icon.className = 'fas fa-spinner fa-spin';
19
+ button.disabled = true;
20
+ }
21
+
22
+ fetch('/generate_audio', {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ body: JSON.stringify({
28
+ text: text,
29
+ model: currentTTS
30
+ })
31
+ })
32
+ .then(response => {
33
+ if (!response.ok) {
34
+ throw new Error(`HTTP error! status: ${response.status}`);
35
+ }
36
+ return response.json();
37
+ })
38
+ .then(data => {
39
+ if (data.audio_url) {
40
+ console.log(`Reproduciendo audio generado con modelo: ${data.model_used}`);
41
+ const audio = new Audio(window.location.origin + data.audio_url);
42
+
43
+ if (button) {
44
+ audio.onplay = () => {
45
+ icon.className = 'fas fa-pause';
46
+ };
47
+
48
+ audio.onended = () => {
49
+ icon.className = 'fas fa-play';
50
+ button.disabled = false;
51
+ };
52
+ }
53
+
54
+ audio.onerror = (e) => {
55
+ console.error('Error reproduciendo audio:', e);
56
+ if (button) {
57
+ icon.className = 'fas fa-play';
58
+ button.disabled = false;
59
+ }
60
+ appendBotMessage("Error reproduciendo el audio. Intente de nuevo.");
61
+ };
62
+
63
+ audio.play().catch(error => {
64
+ console.error('Error playing audio:', error);
65
+ if (button) {
66
+ icon.className = 'fas fa-play';
67
+ button.disabled = false;
68
+ }
69
+ appendBotMessage("Error reproduciendo el audio. Intente de nuevo.");
70
+ });
71
+ } else {
72
+ throw new Error('No audio URL in response');
73
+ }
74
+ })
75
+ .catch(error => {
76
+ console.error('Error:', error);
77
+ if (button) {
78
+ icon.className = 'fas fa-play';
79
+ button.disabled = false;
80
+ }
81
+ appendBotMessage(`Error generando el audio: ${error.message}`);
82
+ });
83
+ }
84
+
85
+ // Agregar esto para manejar nuevos mensajes dinámicamente
86
+ function appendBotMessage(message) {
87
+ const chatMessages = document.getElementById('chat-messages');
88
+ const messageDiv = document.createElement('div');
89
+ messageDiv.className = 'message bot-message';
90
+ messageDiv.innerHTML = `
91
+ <div class="message-content">${message}</div>
92
+ <button onclick="playAudio(this)" data-text="${message}" class="play-button">
93
+ <i class="fas fa-play"></i>
94
+ </button>
95
+ `;
96
+ chatMessages.appendChild(messageDiv);
97
+ }
98
+
99
+ async function sendMessage() {
100
+ const input = document.getElementById('user-input');
101
+ const text = input.value.trim();
102
+
103
+ if (text) {
104
+ // Agregar mensaje del usuario
105
+ const chatMessages = document.getElementById('chat-messages');
106
+ const userDiv = document.createElement('div');
107
+ userDiv.className = 'message user-message';
108
+ userDiv.textContent = text;
109
+ chatMessages.appendChild(userDiv);
110
+
111
+ // Limpiar input
112
+ input.value = '';
113
+
114
+ try {
115
+ // Enviar mensaje al backend con el modo actual
116
+ const response = await fetch('/chat', {
117
+ method: 'POST',
118
+ headers: {
119
+ 'Content-Type': 'application/json',
120
+ },
121
+ body: JSON.stringify({
122
+ message: text,
123
+ mode: currentMode
124
+ })
125
+ });
126
+
127
+ const data = await response.json();
128
+
129
+ if (data.response) {
130
+ // Agregar respuesta del bot
131
+ appendBotMessage(data.response);
132
+ // Auto-reproducir respuesta
133
+ const lastButton = document.querySelector('.bot-message:last-child .play-button');
134
+ if (lastButton) {
135
+ playAudio(lastButton);
136
+ }
137
+ }
138
+ } catch (error) {
139
+ console.error('Error:', error);
140
+ appendBotMessage("Lo siento, hubo un error al procesar tu mensaje.");
141
+ }
142
+ }
143
+ }
144
+
145
+ async function changeMode(mode) {
146
+ try {
147
+ const response = await fetch('/change_mode', {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ },
152
+ body: JSON.stringify({ mode })
153
+ });
154
+
155
+ if (response.ok) {
156
+ currentMode = mode;
157
+ appendBotMessage(`Modo cambiado a: ${mode}`);
158
+ playAudio(null, `Modo de operación cambiado a ${mode}`);
159
+ }
160
+ } catch (error) {
161
+ console.error('Error changing mode:', error);
162
+ appendBotMessage("Error al cambiar el modo de operación");
163
+ }
164
+ }
165
+
166
+ async function changeModel(model) {
167
+ try {
168
+ const response = await fetch('/change_model', {
169
+ method: 'POST',
170
+ headers: {
171
+ 'Content-Type': 'application/json',
172
+ },
173
+ body: JSON.stringify({ model })
174
+ });
175
+
176
+ if (response.ok) {
177
+ currentModel = model;
178
+ appendBotMessage(`Modelo de IA cambiado a: ${model}`);
179
+ playAudio(null, `Modelo de inteligencia artificial cambiado a ${model}`);
180
+ }
181
+ } catch (error) {
182
+ console.error('Error changing model:', error);
183
+ appendBotMessage("Error al cambiar el modelo de IA");
184
+ }
185
+ }
186
+
187
+ // Inicializar reconocimiento de voz
188
+ function initSpeechRecognition() {
189
+ if ('webkitSpeechRecognition' in window) {
190
+ recognition = new webkitSpeechRecognition();
191
+ recognition.continuous = true;
192
+ recognition.interimResults = true;
193
+ recognition.lang = 'es-ES';
194
+
195
+ recognition.onresult = function(event) {
196
+ const result = event.results[event.results.length - 1];
197
+ if (result.isFinal) {
198
+ const text = result.item(0).transcript;
199
+ document.getElementById('user-input').value = text;
200
+ sendMessage();
201
+ }
202
+ };
203
+
204
+ recognition.onerror = function(event) {
205
+ console.error('Error en reconocimiento:', event.error);
206
+ toggleVAD();
207
+ };
208
+ } else {
209
+ console.error('Reconocimiento de voz no soportado');
210
+ }
211
+ }
212
+
213
+ // Función para cambiar TTS
214
+ async function changeTTS(model) {
215
+ try {
216
+ const response = await fetch('/change_tts', {
217
+ method: 'POST',
218
+ headers: {
219
+ 'Content-Type': 'application/json',
220
+ },
221
+ body: JSON.stringify({ model })
222
+ });
223
+
224
+ if (response.ok) {
225
+ currentTTS = model;
226
+ appendBotMessage(`Modelo de voz cambiado a: ${model}`);
227
+ playAudio(null, `Voz del sistema cambiada a ${model}`);
228
+ }
229
+ } catch (error) {
230
+ console.error('Error changing TTS:', error);
231
+ appendBotMessage("Error al cambiar el modelo de voz");
232
+ }
233
+ }
234
+
235
+ // Función para activar/desactivar VAD
236
+ function toggleVAD() {
237
+ const vadButton = document.querySelector('.vad-button');
238
+ const vadStatus = document.getElementById('vad-status');
239
+
240
+ if (!isListening) {
241
+ if (recognition) {
242
+ recognition.start();
243
+ isListening = true;
244
+ vadButton.classList.remove('inactive');
245
+ vadButton.classList.add('active');
246
+ vadButton.innerHTML = '<i class="fas fa-microphone"></i> Escuchando';
247
+
248
+ // Mostrar estado de escucha
249
+ vadStatus.classList.add('listening');
250
+ vadStatus.innerHTML = '🎤 Escuchando...';
251
+
252
+ // Reproducir sonido de inicio
253
+ playAudio(null, "Sistema activado. Puede hablar.");
254
+ }
255
+ } else {
256
+ if (recognition) {
257
+ recognition.stop();
258
+ isListening = false;
259
+ vadButton.classList.remove('active');
260
+ vadButton.classList.add('inactive');
261
+ vadButton.innerHTML = '<i class="fas fa-microphone-slash"></i> Activar micrófono';
262
+
263
+ // Ocultar estado de escucha
264
+ vadStatus.classList.remove('listening');
265
+ vadStatus.innerHTML = '';
266
+
267
+ // Mensaje de desactivación
268
+ playAudio(null, "Sistema en pausa.");
269
+ }
270
+ }
271
+ }
272
+
273
+ // Inicializar cuando el documento esté listo
274
+ document.addEventListener('DOMContentLoaded', function() {
275
+ initSpeechRecognition();
276
+ // Iniciar VAD automáticamente
277
+ setTimeout(() => {
278
+ toggleVAD(); // Activar el VAD al inicio
279
+ appendBotMessage("Sistema iniciado. Modelos cargados correctamente. Escuchando...");
280
+ }, 1000);
281
+ });
282
+
283
+ // Permitir enviar con Enter
284
+ document.getElementById('user-input')?.addEventListener('keypress', function(e) {
285
+ if (e.key === 'Enter') {
286
+ sendMessage();
287
+ }
288
+ });
static/js/main.js ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let recognition = null;
2
+ let isProcessingSpeech = false;
3
+ let isPlayingAudio = false;
4
+ const STOP_WORDS = ['alto', 'detente', 'permiteme', 'callate', 'silencio'];
5
+
6
+ // Elementos del DOM
7
+ const chatBox = document.getElementById('chatBox');
8
+ const textInput = document.getElementById('textInput');
9
+ const sendTextButton = document.getElementById('sendText');
10
+ const modoSelect = document.getElementById('modoSelect');
11
+ const modeloSelect = document.getElementById('modeloSelect');
12
+ const vozSelect = document.getElementById('vozSelect');
13
+ const configForm = document.getElementById('configForm');
14
+ const statusLabel = document.getElementById('statusLabel');
15
+
16
+ // Manejar guardado de configuración
17
+ if (configForm) {
18
+ configForm.addEventListener('submit', async (e) => {
19
+ e.preventDefault();
20
+ const config = {
21
+ GOOGLE_API_KEY: document.getElementById('googleApiKey').value,
22
+ HUGGINGFACE_TOKEN: document.getElementById('huggingfaceToken').value,
23
+ OPENAI_API_KEY: document.getElementById('openaiApiKey').value
24
+ };
25
+
26
+ try {
27
+ const response = await fetch('/guardar_config', {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json'
31
+ },
32
+ body: JSON.stringify(config)
33
+ });
34
+
35
+ const data = await response.json();
36
+ if (data.success) {
37
+ alert('Configuración guardada exitosamente');
38
+ } else {
39
+ alert('Error al guardar la configuración: ' + data.error);
40
+ }
41
+ } catch (error) {
42
+ alert('Error al guardar la configuración: ' + error);
43
+ }
44
+ });
45
+ }
46
+
47
+ // Manejar cambio de modelo AI
48
+ if (modeloSelect) {
49
+ modeloSelect.addEventListener('change', async function() {
50
+ const response = await fetch('/cambiar_modelo', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json'
54
+ },
55
+ body: JSON.stringify({ modelo: this.value })
56
+ });
57
+
58
+ const data = await response.json();
59
+ if (!data.success) {
60
+ alert('Error al cambiar el modelo: ' + data.error);
61
+ }
62
+ });
63
+ }
64
+
65
+ // Manejar cambio de modelo de voz
66
+ if (vozSelect) {
67
+ vozSelect.addEventListener('change', async function() {
68
+ const response = await fetch('/cambiar_voz', {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Content-Type': 'application/json'
72
+ },
73
+ body: JSON.stringify({ voz: this.value })
74
+ });
75
+
76
+ const data = await response.json();
77
+ if (!data.success) {
78
+ alert('Error al cambiar el modelo de voz: ' + data.error);
79
+ }
80
+ });
81
+ }
82
+
83
+ // Manejar cambio de modo
84
+ modoSelect.addEventListener('change', async function() {
85
+ const response = await fetch('/cambiar_modo', {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json'
89
+ },
90
+ body: JSON.stringify({ modo: this.value })
91
+ });
92
+
93
+ const data = await response.json();
94
+ if (data.success) {
95
+ addMessage(data.mensaje, 'bot');
96
+ if (data.audio) {
97
+ await playAudio(data.audio);
98
+ }
99
+ updateStatus('Esperando activación...');
100
+ }
101
+ });
102
+
103
+ // Función para verificar si el texto contiene palabras de parada
104
+ function containsStopWord(text) {
105
+ const lowerText = text.toLowerCase();
106
+ return STOP_WORDS.some(word => lowerText.includes(word));
107
+ }
108
+
109
+ // Función para reproducir audio
110
+ async function playAudio(base64Audio) {
111
+ try {
112
+ console.log('Preparando reproducción de audio...');
113
+ isPlayingAudio = true;
114
+
115
+ // Detener completamente el reconocimiento
116
+ if (recognition) {
117
+ recognition.stop();
118
+ recognition.abort();
119
+ await new Promise(resolve => setTimeout(resolve, 500));
120
+ }
121
+
122
+ updateStatus('Reproduciendo respuesta...');
123
+ const audio = new Audio('data:audio/mp3;base64,' + base64Audio);
124
+
125
+ // Permitir interrupciones durante la reproducción
126
+ recognition.start();
127
+
128
+ audio.onended = () => {
129
+ console.log('Audio reproducido completamente');
130
+ isPlayingAudio = false;
131
+ updateStatus('Escuchando...');
132
+ };
133
+
134
+ audio.play();
135
+
136
+ } catch (error) {
137
+ console.error('Error reproduciendo audio:', error);
138
+ isPlayingAudio = false;
139
+ updateStatus('Error al reproducir audio. Escuchando...');
140
+ if (!isProcessingSpeech) {
141
+ recognition.start();
142
+ }
143
+ }
144
+ }
145
+
146
+ // Función para agregar mensaje al chat
147
+ function addMessage(text, sender) {
148
+ if (!text || !sender) {
149
+ console.error('Error: texto o remitente faltante', { text, sender });
150
+ return;
151
+ }
152
+
153
+ const chatBox = document.getElementById('chatBox');
154
+ if (!chatBox) {
155
+ console.error('Error crítico: No se encontró el elemento chatBox');
156
+ return;
157
+ }
158
+
159
+ try {
160
+ console.log('Agregando mensaje al chat:', { text, sender });
161
+
162
+ const messageDiv = document.createElement('div');
163
+ messageDiv.className = `message ${sender}-message`;
164
+
165
+ const iconSpan = document.createElement('span');
166
+ iconSpan.className = 'message-icon';
167
+ iconSpan.textContent = sender === 'user' ? '👤' : '🤖';
168
+
169
+ const textSpan = document.createElement('span');
170
+ textSpan.className = 'message-text';
171
+ textSpan.textContent = text;
172
+
173
+ messageDiv.appendChild(iconSpan);
174
+ messageDiv.appendChild(textSpan);
175
+ chatBox.appendChild(messageDiv);
176
+
177
+ // Hacer scroll al último mensaje
178
+ chatBox.scrollTop = chatBox.scrollHeight;
179
+
180
+ console.log('Mensaje agregado exitosamente');
181
+ } catch (error) {
182
+ console.error('Error al agregar mensaje:', error);
183
+ }
184
+ }
185
+
186
+ // Agregar múltiples mensajes
187
+ function addMessages(messages) {
188
+ messages.forEach(msg => {
189
+ addMessage(msg.text, msg.sender);
190
+ });
191
+ }
192
+
193
+ // Función para actualizar el estado
194
+ function updateStatus(text) {
195
+ const statusLabel = document.getElementById('statusLabel');
196
+ if (statusLabel) {
197
+ statusLabel.textContent = text;
198
+ statusLabel.style.display = 'block'; // Asegurar que sea visible
199
+ console.log('Estado actualizado:', text);
200
+ } else {
201
+ console.error('No se encontró el elemento statusLabel');
202
+ }
203
+ }
204
+
205
+ // Función para procesar la respuesta del servidor
206
+ async function processServerResponse(data, userText) {
207
+ try {
208
+ console.log('Procesando respuesta del servidor:', data);
209
+
210
+ // Mostrar el mensaje del usuario
211
+ addMessage(userText, 'user');
212
+
213
+ if (data.success && data.texto) {
214
+ // Mostrar la respuesta del bot
215
+ console.log('Mostrando respuesta del bot:', data.texto);
216
+ addMessage(data.texto, 'bot');
217
+
218
+ // Reproducir el audio si existe y no se dijo una palabra de parada
219
+ if (data.audio && !containsStopWord(userText)) {
220
+ await playAudio(data.audio);
221
+ }
222
+ } else {
223
+ const errorMessage = data.error || 'Error desconocido';
224
+ console.error('Error en la respuesta:', errorMessage);
225
+ addMessage('Lo siento, hubo un error: ' + errorMessage, 'bot');
226
+ }
227
+ } catch (error) {
228
+ console.error('Error procesando respuesta:', error);
229
+ addMessage('Lo siento, ocurrió un error inesperado.', 'bot');
230
+ }
231
+ }
232
+
233
+ // Inicializar reconocimiento de voz
234
+ function initializeSpeechRecognition() {
235
+ if ('webkitSpeechRecognition' in window) {
236
+ recognition = new webkitSpeechRecognition();
237
+ recognition.continuous = true; // Cambiar a true para permitir interrupciones
238
+ recognition.interimResults = true;
239
+ recognition.lang = 'es-ES';
240
+
241
+ recognition.onstart = function() {
242
+ console.log('Reconocimiento de voz iniciado');
243
+ updateStatus('Escuchando...');
244
+ };
245
+
246
+ recognition.onend = function() {
247
+ console.log('Reconocimiento de voz terminado');
248
+ if (!isProcessingSpeech) {
249
+ console.log('Reiniciando reconocimiento...');
250
+ updateStatus('Escuchando...');
251
+ setTimeout(() => recognition.start(), 500);
252
+ }
253
+ };
254
+
255
+ recognition.onerror = function(event) {
256
+ console.error('Error en reconocimiento de voz:', event.error);
257
+ if (!isProcessingSpeech && event.error !== 'no-speech') {
258
+ setTimeout(() => recognition.start(), 1000);
259
+ }
260
+ };
261
+
262
+ recognition.onresult = async function(event) {
263
+ let finalTranscript = '';
264
+ let interimTranscript = '';
265
+
266
+ for (let i = event.resultIndex; i < event.results.length; ++i) {
267
+ const transcript = event.results[i][0].transcript;
268
+
269
+ if (event.results[i].isFinal) {
270
+ finalTranscript = transcript;
271
+ console.log('Texto final:', finalTranscript);
272
+
273
+ // Verificar si es una palabra de parada
274
+ if (containsStopWord(finalTranscript)) {
275
+ console.log('Palabra de parada detectada, deteniendo audio...');
276
+ const audio = document.querySelector('audio');
277
+ if (audio) {
278
+ audio.pause();
279
+ audio.currentTime = 0;
280
+ }
281
+ isPlayingAudio = false;
282
+ updateStatus('Audio detenido. Escuchando...');
283
+ continue;
284
+ }
285
+
286
+ if (!isProcessingSpeech) {
287
+ isProcessingSpeech = true;
288
+ updateStatus('Procesando...');
289
+
290
+ try {
291
+ console.log('Enviando texto al servidor:', finalTranscript);
292
+ const response = await fetch('/procesar_voz', {
293
+ method: 'POST',
294
+ headers: {
295
+ 'Content-Type': 'application/json'
296
+ },
297
+ body: JSON.stringify({ texto: finalTranscript })
298
+ });
299
+
300
+ const data = await response.json();
301
+ console.log('Respuesta recibida:', data);
302
+ await processServerResponse(data, finalTranscript);
303
+
304
+ } catch (error) {
305
+ console.error('Error al procesar voz:', error);
306
+ updateStatus('Error de conexión. Escuchando...');
307
+ addMessage('Lo siento, hubo un error de conexión.', 'bot');
308
+ }
309
+
310
+ isProcessingSpeech = false;
311
+ }
312
+ } else {
313
+ interimTranscript = transcript;
314
+ updateStatus(`Escuchando: ${interimTranscript}`);
315
+ }
316
+ }
317
+ };
318
+
319
+ // Iniciar reconocimiento
320
+ recognition.start();
321
+ updateStatus('Escuchando...');
322
+
323
+ } else {
324
+ console.error('El reconocimiento de voz no está soportado en este navegador');
325
+ alert('Tu navegador no soporta el reconocimiento de voz. Por favor, usa Chrome.');
326
+ updateStatus('Reconocimiento de voz no soportado');
327
+ }
328
+ }
329
+
330
+ // Manejar envío de texto
331
+ sendTextButton.addEventListener('click', async () => {
332
+ const text = textInput.value.trim();
333
+ if (text) {
334
+ updateStatus('Procesando...');
335
+ addMessage(text, 'user');
336
+
337
+ try {
338
+ const response = await fetch('/procesar_voz', {
339
+ method: 'POST',
340
+ headers: {
341
+ 'Content-Type': 'application/json'
342
+ },
343
+ body: JSON.stringify({ texto: text })
344
+ });
345
+
346
+ const data = await response.json();
347
+ await processServerResponse(data, text);
348
+
349
+ textInput.value = '';
350
+ updateStatus('Esperando activación...');
351
+ } catch (error) {
352
+ console.error('Error:', error);
353
+ updateStatus('Error de conexión');
354
+ }
355
+ }
356
+ });
357
+
358
+ // Inicializar cuando se carga la página
359
+ document.addEventListener('DOMContentLoaded', () => {
360
+ console.log('Página cargada, verificando elementos...');
361
+
362
+ // Verificar que existan los elementos necesarios
363
+ const chatBox = document.getElementById('chatBox');
364
+ const statusLabel = document.getElementById('statusLabel');
365
+
366
+ if (!chatBox) {
367
+ console.error('Error crítico: No se encontró el elemento chatBox');
368
+ return;
369
+ }
370
+
371
+ if (!statusLabel) {
372
+ console.error('Error crítico: No se encontró el elemento statusLabel');
373
+ return;
374
+ }
375
+
376
+ // Asegurar que el statusLabel sea visible
377
+ statusLabel.style.display = 'block';
378
+
379
+ console.log('Elementos encontrados, inicializando reconocimiento de voz...');
380
+ initializeSpeechRecognition();
381
+
382
+ // Agregar mensaje de bienvenida
383
+ addMessage('¡Hola! Soy tu asistente virtual. ¿En qué puedo ayudarte?', 'bot');
384
+ });
templates/.gitkeep ADDED
File without changes
templates/chat.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chatbot</title>
7
+ <link rel="stylesheet" href="/static/css/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="chat-container">
11
+ <div id="chat-messages" class="chat-messages"></div>
12
+ <div class="chat-input">
13
+ <input type="text" id="user-input" placeholder="Escribe tu mensaje...">
14
+ <button id="send-button">Enviar</button>
15
+ </div>
16
+ </div>
17
+ <script src="/static/js/chat.js"></script>
18
+ </body>
19
+ </html>
templates/index.html ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chatbot</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ background-color: #f5f5f5;
11
+ font-family: Arial, sans-serif;
12
+ }
13
+
14
+ .chat-container {
15
+ max-width: 800px;
16
+ margin: 20px auto;
17
+ padding: 20px;
18
+ background-color: white;
19
+ border-radius: 10px;
20
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
21
+ }
22
+
23
+ #chatBox {
24
+ height: 400px;
25
+ overflow-y: auto;
26
+ padding: 20px;
27
+ border: 1px solid #ddd;
28
+ border-radius: 5px;
29
+ margin-bottom: 20px;
30
+ background-color: #f8f9fa;
31
+ }
32
+
33
+ .message {
34
+ margin: 10px 0;
35
+ padding: 12px;
36
+ border-radius: 10px;
37
+ max-width: 80%;
38
+ position: relative;
39
+ animation: fadeIn 0.3s ease-in;
40
+ display: flex;
41
+ align-items: flex-start;
42
+ }
43
+
44
+ .message-icon {
45
+ margin-right: 8px;
46
+ font-size: 1.2em;
47
+ }
48
+
49
+ .message-text {
50
+ word-wrap: break-word;
51
+ flex: 1;
52
+ }
53
+
54
+ .user-message {
55
+ background-color: #007bff;
56
+ color: white;
57
+ margin-left: auto;
58
+ flex-direction: row-reverse;
59
+ }
60
+
61
+ .user-message .message-icon {
62
+ margin-right: 0;
63
+ margin-left: 8px;
64
+ }
65
+
66
+ .bot-message {
67
+ background-color: #e9ecef;
68
+ color: black;
69
+ margin-right: auto;
70
+ }
71
+
72
+ .control-panel {
73
+ background-color: #f8f9fa;
74
+ padding: 15px;
75
+ border-radius: 5px;
76
+ margin-bottom: 20px;
77
+ border: 1px solid #dee2e6;
78
+ }
79
+
80
+ .status-label {
81
+ font-size: 1em;
82
+ color: #333;
83
+ margin-top: 15px;
84
+ text-align: center;
85
+ font-style: italic;
86
+ padding: 10px;
87
+ background-color: #f8f9fa;
88
+ border-radius: 5px;
89
+ border: 1px solid #dee2e6;
90
+ display: block;
91
+ }
92
+
93
+ .recording-button {
94
+ transition: all 0.3s ease;
95
+ width: 50px;
96
+ height: 38px;
97
+ padding: 0;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ font-size: 1.2em;
102
+ }
103
+
104
+ .recording-button.active {
105
+ background-color: #dc3545 !important;
106
+ animation: pulse 1.5s infinite;
107
+ }
108
+
109
+ @keyframes pulse {
110
+ 0% { transform: scale(1); }
111
+ 50% { transform: scale(1.05); }
112
+ 100% { transform: scale(1); }
113
+ }
114
+
115
+ @keyframes fadeIn {
116
+ from { opacity: 0; transform: translateY(10px); }
117
+ to { opacity: 1; transform: translateY(0); }
118
+ }
119
+
120
+ .input-group {
121
+ margin: 5px 15px;
122
+ }
123
+
124
+ #textInput {
125
+ border-radius: 5px 0 0 5px;
126
+ border: 1px solid #ced4da;
127
+ padding: 8px 12px;
128
+ }
129
+
130
+ #sendText {
131
+ border-radius: 0;
132
+ margin-right: -1px;
133
+ }
134
+
135
+ #startRecording {
136
+ border-radius: 0 5px 5px 0;
137
+ }
138
+
139
+ .form-select {
140
+ cursor: pointer;
141
+ }
142
+
143
+ .form-label {
144
+ font-weight: 500;
145
+ margin-bottom: 5px;
146
+ }
147
+
148
+ #ngrokUrl {
149
+ font-size: 0.9em;
150
+ background-color: #f8f9fa;
151
+ border-color: #dee2e6;
152
+ min-width: 250px;
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="container mt-4">
158
+ <ul class="nav nav-tabs" id="myTab" role="tablist">
159
+ <li class="nav-item" role="presentation">
160
+ <button class="nav-link active" id="chat-tab" data-bs-toggle="tab" data-bs-target="#chat" type="button" role="tab">💬 Chat</button>
161
+ </li>
162
+ {% if config.NGROK_URL %}
163
+ <li class="nav-item ms-auto d-flex align-items-center">
164
+ <div class="input-group">
165
+ <input type="text" class="form-control" value="{{ config.NGROK_URL }}" id="ngrokUrl" readonly>
166
+ <button class="btn btn-primary" onclick="shareUrl()">🔗 Copiar URL</button>
167
+ </div>
168
+ </li>
169
+ {% endif %}
170
+ </ul>
171
+
172
+ <div class="tab-content" id="myTabContent">
173
+ <!-- Tab de Chat -->
174
+ <div class="tab-pane fade show active" id="chat" role="tabpanel">
175
+ <div class="chat-container">
176
+ <div class="control-panel">
177
+ <div class="row">
178
+ <div class="col-md-4">
179
+ <label for="modoSelect" class="form-label">📋 Modo:</label>
180
+ <select id="modoSelect" class="form-select">
181
+ <option value="seguros">🛡️ Seguros</option>
182
+ <option value="credito">💰 Créditos</option>
183
+ <option value="cobranza">💵 Cobranza</option>
184
+ </select>
185
+ </div>
186
+ <div class="col-md-4">
187
+ <label for="modeloSelect" class="form-label">🤖 Modelo:</label>
188
+ <select id="modeloSelect" class="form-select">
189
+ <option value="gemini">🧠 Gemini 8b</option>
190
+ <option value="mixtral">⚡Mixtral 7b</option>
191
+ </select>
192
+ </div>
193
+ <div class="col-md-4">
194
+ <label for="vozSelect" class="form-label">🎤 Voz:</label>
195
+ <select id="vozSelect" class="form-select">
196
+ <option value="EDGE">📢 Edge</option>
197
+ <option value="VITS">🔊 VITS</option>
198
+ <option value="gTTS">🌐 gTTS</option>
199
+ </select>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <div id="chatBox"></div>
205
+
206
+ <div class="input-group">
207
+ <input type="text" id="textInput" class="form-control" placeholder="Escribe tu mensaje...">
208
+ <button id="sendText" class="btn btn-primary">Enviar</button>
209
+ <button id="startRecording" class="btn btn-success recording-button">🎤</button>
210
+ </div>
211
+
212
+ <div class="status-label" id="statusLabel">Iniciando...</div>
213
+ </div>
214
+ </div>
215
+
216
+ <!-- Tab de Configuración -->
217
+ <div class="tab-pane fade" id="config" role="tabpanel">
218
+ <div class="chat-container">
219
+ <form id="configForm">
220
+ <div class="mb-3">
221
+ <label for="googleApiKey" class="form-label">Google API Key:</label>
222
+ <input type="password" class="form-control" id="googleApiKey" value="{{ config.get('GOOGLE_API_KEY', '') }}">
223
+ </div>
224
+ <div class="mb-3">
225
+ <label for="huggingfaceToken" class="form-label">HuggingFace Token:</label>
226
+ <input type="password" class="form-control" id="huggingfaceToken" value="{{ config.get('HUGGINGFACE_TOKEN', '') }}">
227
+ </div>
228
+ <div class="mb-3">
229
+ <label for="openaiApiKey" class="form-label">OpenAI API Key:</label>
230
+ <input type="password" class="form-control" id="openaiApiKey" value="{{ config.get('OPENAI_API_KEY', '') }}">
231
+ </div>
232
+ <button type="submit" class="btn btn-primary">Guardar</button>
233
+ </form>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
240
+ <script src="{{ url_for('static', filename='js/main.js') }}"></script>
241
+ <script>
242
+ function shareUrl() {
243
+ const url = document.getElementById('ngrokUrl').value;
244
+ navigator.clipboard.writeText(url).then(() => {
245
+ const btn = document.querySelector('button[onclick="shareUrl()"]');
246
+ const originalText = btn.innerHTML;
247
+ btn.innerHTML = '✅ ¡Copiado!';
248
+ setTimeout(() => {
249
+ btn.innerHTML = originalText;
250
+ }, 2000);
251
+ }).catch(err => {
252
+ alert("URL del túnel: " + url);
253
+ });
254
+ }
255
+ </script>
256
+ </body>
257
+ </html>