salomonsky commited on
Commit
4b16238
·
verified ·
1 Parent(s): 56069b3

Upload main.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. main.js +671 -294
main.js CHANGED
@@ -1,7 +1,31 @@
 
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');
@@ -12,373 +36,726 @@ 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
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Variables globales
2
  let recognition = null;
3
  let isProcessingSpeech = false;
4
  let isPlayingAudio = false;
5
+ let currentMode = 'soporte';
6
+ let currentModel = 'gemini';
7
+ let currentTTS = 'EDGE';
8
+ let isListening = false;
9
+ let sessionId = null;
10
+ let shouldRestartRecognition = true;
11
+
12
+ // Variables globales para audio
13
+ let currentAudio = null;
14
+ let isPlaying = false;
15
+ let audioQueue = [];
16
+ let isProcessingQueue = false;
17
+
18
+ // Variables para control de interrupción
19
+ let lastInterruptTime = 0;
20
+ const INTERRUPT_COOLDOWN = 1000; // 1 segundo entre interrupciones
21
+ const ENERGY_THRESHOLD = 1.5; // Umbral de energía para detectar interrupción
22
+ let audioContext = null;
23
+ let analyser = null;
24
+ let microphoneStream = null;
25
+
26
+ const STOP_WORDS = ['alto', 'detente', 'permiteme', 'callate', 'silencio', 'calla', 'para', 'espera'];
27
+ const GREETING_WORDS = ['hola', 'buenas', 'buenos días', 'buenas tardes', 'buenas noches'];
28
+ const INTEREST_WORDS = ['acepto', 'me interesa', 'dime más', 'cuéntame más', 'quiero saber más'];
29
 
30
  // Elementos del DOM
31
  const chatBox = document.getElementById('chatBox');
 
36
  const vozSelect = document.getElementById('vozSelect');
37
  const configForm = document.getElementById('configForm');
38
  const statusLabel = document.getElementById('statusLabel');
39
+ const startRecordingButton = document.getElementById('startRecording');
40
 
41
+ // Comandos de voz
42
+ const VOICE_COMMANDS = {
43
+ stop: ['para', 'detente', 'silencio', 'cállate', 'espera', 'stop', 'alto', 'basta', 'suficiente', 'ya', 'shh', 'sh'],
44
+ greet: ['hola', 'buenas', 'buenos días', 'buenas tardes', 'buenas noches'],
45
+ interest: ['acepto', 'me interesa', 'dime más', 'cuéntame más', 'quiero saber más']
46
+ };
47
+
48
+ // Funciones de utilidad
49
+ const Utils = {
50
+ checkCommand(text, type) {
51
+ return VOICE_COMMANDS[type].some(cmd => text.toLowerCase().includes(cmd));
52
+ },
53
+
54
+ updateStatus(text, type = 'info') {
55
+ if (statusLabel) {
56
+ statusLabel.textContent = text;
57
+ statusLabel.classList.remove('error', 'success');
58
+ if (type === 'error' || type === 'success') {
59
+ statusLabel.classList.add(type);
60
+ }
61
+ }
62
+ }
63
+ };
64
 
65
+ // Funciones de chat
66
+ const ChatManager = {
67
+ addMessage(text, sender) {
68
+ if (!text || !sender || !chatBox) return;
69
+
70
+ try {
71
+ console.log('Agregando mensaje al chat:', { text, sender });
72
+
73
+ const messageDiv = document.createElement('div');
74
+ messageDiv.className = `message ${sender}-message`;
75
+
76
+ const iconSpan = document.createElement('span');
77
+ iconSpan.className = 'message-icon';
78
+ iconSpan.textContent = sender === 'user' ? '👤' : '🤖';
79
+
80
+ const textSpan = document.createElement('span');
81
+ textSpan.className = 'message-text';
82
+ textSpan.textContent = text;
83
+
84
+ messageDiv.appendChild(iconSpan);
85
+ messageDiv.appendChild(textSpan);
86
+ chatBox.appendChild(messageDiv);
87
+
88
+ // Hacer scroll al último mensaje
89
+ chatBox.scrollTop = chatBox.scrollHeight;
90
+
91
+ console.log('Mensaje agregado exitosamente');
92
+ } catch (error) {
93
+ console.error('Error al agregar mensaje:', error);
94
+ }
95
+ },
96
+
97
+ async sendMessage(text) {
98
+ if (!text) return;
99
+
100
  try {
101
+ Utils.updateStatus('Procesando...');
102
+ this.addMessage(text, 'user');
103
+ textInput.value = '';
104
+
105
+ const response = await fetch('/chat', {
106
  method: 'POST',
107
  headers: {
108
  'Content-Type': 'application/json'
109
  },
110
+ body: JSON.stringify({
111
+ mensaje: text,
112
+ mode: currentMode,
113
+ model: currentModel,
114
+ tts: currentTTS
115
+ })
116
  });
117
+
118
+ if (!response.ok) {
119
+ throw new Error(`Error HTTP: ${response.status}`);
120
+ }
121
+
122
  const data = await response.json();
123
+ if (data.success && data.texto) {
124
+ this.addMessage(data.texto, 'bot');
125
+
126
+ if (data.audio) {
127
+ try {
128
+ const audioType = data.audio_type || 'mp3';
129
+ await AudioManager.playAudio(data.audio, audioType);
130
+ } catch (audioError) {
131
+ console.error('Error reproduciendo audio:', audioError);
132
+ handleAudioError();
133
+ }
134
+ }
135
  } else {
136
+ throw new Error(data.error || 'Error desconocido');
137
  }
138
+
139
  } catch (error) {
140
+ console.error('Error:', error);
141
+ Utils.updateStatus('Error de conexión', 'error');
142
+ this.addMessage('Lo siento, hubo un error al procesar tu mensaje.', 'bot');
143
+ } finally {
144
+ Utils.updateStatus('Escuchando...');
145
  }
146
+ }
147
+ };
148
 
149
+ // Gestión de audio
150
+ const AudioManager = {
151
+ async playAudio(base64Audio, audioType = 'wav') {
152
+ if (!base64Audio) return;
153
+
154
+ try {
155
+ if (isPlayingAudio) {
156
+ audioQueue.push({ base64Audio, audioType });
157
+ return;
158
+ }
159
+
160
+ isPlayingAudio = true;
161
+ Utils.updateStatus('Reproduciendo audio...');
162
+
163
+ const audioBlob = await fetch(`data:audio/${audioType};base64,${base64Audio}`)
164
+ .then(res => res.blob());
165
+
166
+ currentAudio = new Audio(URL.createObjectURL(audioBlob));
167
+
168
+ currentAudio.onended = () => {
169
+ isPlayingAudio = false;
170
+ URL.revokeObjectURL(currentAudio.src);
171
+ currentAudio = null;
172
+ Utils.updateStatus('Escuchando...');
173
+ this.processQueue();
174
+ };
175
+
176
+ await currentAudio.play();
177
+
178
+ } catch (error) {
179
+ console.error('Error reproduciendo audio:', error);
180
+ this.handleError();
181
+ }
182
+ },
183
+
184
+ async processQueue() {
185
+ if (isProcessingQueue || audioQueue.length === 0) return;
186
+
187
+ isProcessingQueue = true;
188
+ const nextAudio = audioQueue.shift();
189
+ await this.playAudio(nextAudio.base64Audio, nextAudio.audioType);
190
+ isProcessingQueue = false;
191
+ },
192
+
193
+ handleError() {
194
+ isPlayingAudio = false;
195
+ if (currentAudio) {
196
+ URL.revokeObjectURL(currentAudio.src);
197
+ currentAudio = null;
198
+ }
199
+ Utils.updateStatus('Error de audio', 'error');
200
+ },
201
+
202
+ stop() {
203
+ if (currentAudio) {
204
+ currentAudio.pause();
205
+ URL.revokeObjectURL(currentAudio.src);
206
+ currentAudio = null;
207
+ }
208
+ isPlayingAudio = false;
209
+ audioQueue = [];
210
+ Utils.updateStatus('Audio detenido');
211
+ }
212
+ };
213
 
214
+ // Gestión de voz
215
+ const VoiceManager = {
216
+ async processCommand(text) {
217
+ if (!text) return false;
218
+
219
+ const currentTime = Date.now();
220
+
221
+ if (Utils.checkCommand(text, 'stop')) {
222
+ if (currentTime - lastInterruptTime > INTERRUPT_COOLDOWN) {
223
+ lastInterruptTime = currentTime;
224
+ AudioManager.stop();
225
+ Utils.updateStatus('Audio detenido por comando de voz');
226
+ return true;
227
+ }
228
  }
229
+
230
+ if (Utils.checkCommand(text, 'greet') || Utils.checkCommand(text, 'interest')) {
231
+ if (currentTime - lastInterruptTime > INTERRUPT_COOLDOWN) {
232
+ lastInterruptTime = currentTime;
233
+
234
+ if (isPlayingAudio) {
235
+ AudioManager.stop();
236
+ await new Promise(resolve => setTimeout(resolve, 300));
237
+ }
238
+
239
+ ChatManager.addMessage(text, 'user');
240
+ await this.processVoiceResponse(text);
241
+ return true;
242
+ }
243
+ }
244
+
245
+ return false;
246
+ },
247
+
248
+ async processVoiceResponse(text) {
249
+ try {
250
+ shouldRestartRecognition = false;
251
+ if (recognition && isListening) {
252
+ recognition.stop();
253
+ }
254
+
255
+ const response = await fetch('/procesar_voz', {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({
259
+ texto: text,
260
+ mode: currentMode,
261
+ model: currentModel,
262
+ tts: currentTTS
263
+ })
264
+ });
265
+
266
+ if (!response.ok) throw new Error(`Error HTTP: ${response.status}`);
267
+
268
+ const data = await response.json();
269
+ if (data.success && data.texto) {
270
+ ChatManager.addMessage(data.texto, 'bot');
271
+ if (data.audio) {
272
+ await AudioManager.playAudio(data.audio, data.audio_type || 'wav');
273
+ }
274
+ }
275
+ } catch (error) {
276
+ console.error('Error procesando voz:', error);
277
+ Utils.updateStatus('Error procesando voz', 'error');
278
+ } finally {
279
+ shouldRestartRecognition = true;
280
+ if (!isListening) {
281
+ await this.startRecognition();
282
+ }
283
+ }
284
+ },
285
+
286
+ async startRecognition() {
287
+ if (!recognition || !isListening) {
288
+ try {
289
+ await this.initializeSpeechRecognition();
290
+ isListening = true;
291
+ startRecordingButton.classList.add('active');
292
+ Utils.updateStatus('Escuchando...');
293
+ } catch (error) {
294
+ console.error('Error iniciando reconocimiento:', error);
295
+ Utils.updateStatus('Error de micrófono', 'error');
296
+ }
297
+ }
298
+ },
299
+
300
+ async pauseRecognition() {
301
+ if (recognition && isListening) {
302
+ recognition.stop();
303
+ isListening = false;
304
+ startRecordingButton.classList.remove('active');
305
+ Utils.updateStatus('Reconocimiento pausado');
306
+ }
307
+ },
308
+
309
+ async initializeSpeechRecognition() {
310
+ if (!('webkitSpeechRecognition' in window)) {
311
+ throw new Error('Reconocimiento de voz no soportado');
312
+ }
313
+
314
+ recognition = new webkitSpeechRecognition();
315
+ recognition.continuous = true;
316
+ recognition.interimResults = true;
317
+ recognition.lang = 'es-ES';
318
+
319
+ recognition.onstart = () => {
320
+ isListening = true;
321
+ Utils.updateStatus('Escuchando...');
322
+ };
323
+
324
+ recognition.onend = () => {
325
+ isListening = false;
326
+ if (shouldRestartRecognition) {
327
+ recognition.start();
328
+ }
329
+ };
330
+
331
+ recognition.onresult = async (event) => {
332
+ if (isProcessingSpeech) return;
333
+
334
+ const results = Array.from(event.results);
335
+ const lastResult = results[results.length - 1];
336
+
337
+ if (lastResult.isFinal) {
338
+ const text = lastResult[0].transcript.trim();
339
+ if (text) {
340
+ isProcessingSpeech = true;
341
+ await this.processCommand(text);
342
+ isProcessingSpeech = false;
343
+ }
344
+ }
345
+ };
346
+
347
+ recognition.onerror = (event) => {
348
+ console.error('Error de reconocimiento:', event.error);
349
+ Utils.updateStatus('Error de reconocimiento', 'error');
350
+ };
351
+
352
+ recognition.start();
353
+ }
354
+ };
355
+
356
+ // Funciones de modo y modelo
357
+ function changeMode(mode) {
358
+ currentMode = mode;
359
+ fetch('/cambiar_modo', {
360
+ method: 'POST',
361
+ headers: {
362
+ 'Content-Type': 'application/json'
363
+ },
364
+ body: JSON.stringify({ mode })
365
+ })
366
+ .then(response => response.json())
367
+ .then(data => {
368
+ if (data.success) {
369
+ ChatManager.addMessage(`Modo cambiado a: ${mode}`, 'bot');
370
+ } else {
371
+ console.error('Error cambiando modo:', data.error);
372
+ }
373
+ })
374
+ .catch(error => console.error('Error:', error));
375
+ }
376
+
377
+ function changeModel(model) {
378
+ currentModel = model;
379
+ fetch('/cambiar_modelo', {
380
+ method: 'POST',
381
+ headers: {
382
+ 'Content-Type': 'application/json'
383
+ },
384
+ body: JSON.stringify({ model })
385
+ })
386
+ .then(response => response.json())
387
+ .then(data => {
388
+ if (data.success) {
389
+ ChatManager.addMessage(`Modelo cambiado a: ${model}`, 'bot');
390
+ } else {
391
+ console.error('Error cambiando modelo:', data.error);
392
+ }
393
+ })
394
+ .catch(error => console.error('Error:', error));
395
  }
396
 
397
+ async function changeTTS(model) {
398
+ console.log("Cambiando modelo TTS a:", model);
399
+ Utils.updateStatus("Cambiando voz...");
400
+
401
+ try {
402
+ const response = await fetch('/cambiar_tts', {
403
  method: 'POST',
404
  headers: {
405
  'Content-Type': 'application/json'
406
  },
407
+ body: JSON.stringify({ model: model })
408
  });
409
 
410
+ if (!response.ok) {
411
+ throw new Error('Error en la respuesta del servidor');
 
412
  }
 
 
413
 
414
+ const data = await response.json();
415
+
416
+ let voiceName;
417
+ switch(model) {
418
+ case 'EDGE':
419
+ voiceName = "Jorge (MX)";
420
+ break;
421
+ case 'EDGE_ES':
422
+ voiceName = "Álvaro (ES)";
423
+ break;
424
+ case 'VITS':
425
+ voiceName = "VITS (ES)";
426
+ break;
427
+ default:
428
+ voiceName = model;
429
  }
430
+
431
+ Utils.updateStatus(`Voz cambiada a ${voiceName}`);
432
+ console.log("Modelo TTS cambiado exitosamente");
433
+ } catch (error) {
434
+ console.error("Error al cambiar el modelo de voz:", error);
435
+ Utils.updateStatus("Error al cambiar la voz");
436
  }
 
 
 
 
 
 
437
  }
438
 
439
+ // Reconocimiento de voz
440
+ async function checkMicrophonePermissions() {
441
  try {
442
+ // Verificar si ya tenemos permisos
443
+ const permissionStatus = await navigator.permissions.query({ name: 'microphone' });
444
 
445
+ if (permissionStatus.state === 'granted') {
446
+ return true;
447
+ } else if (permissionStatus.state === 'prompt') {
448
+ // Solicitar permisos explícitamente
449
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
450
+ stream.getTracks().forEach(track => track.stop());
451
+ return true;
452
+ } else if (permissionStatus.state === 'denied') {
453
+ Utils.updateStatus('Permisos de micrófono denegados. Por favor, habilítalos en la configuración del navegador.', 'error');
454
+ ChatManager.addMessage('❌ El micrófono está bloqueado. Para habilitarlo:\n1. Haz clic en el icono 🔒 o 🎤 en la barra de direcciones\n2. Selecciona "Permitir" para el micrófono\n3. Recarga la página', 'bot');
455
+ return false;
456
  }
457
+ } catch (error) {
458
+ console.error('Error verificando permisos:', error);
459
+ return false;
460
+ }
461
+ }
462
+
463
+ // Función para inicializar el análisis de audio
464
+ async function setupAudioAnalysis() {
465
+ try {
466
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
467
+ analyser = audioContext.createAnalyser();
468
+ analyser.fftSize = 256;
469
 
470
+ // Configurar filtros para reducir eco
471
+ const lowpass = audioContext.createBiquadFilter();
472
+ lowpass.type = 'lowpass';
473
+ lowpass.frequency.value = 2000;
474
 
475
+ const highpass = audioContext.createBiquadFilter();
476
+ highpass.type = 'highpass';
477
+ highpass.frequency.value = 85;
478
 
479
+ // Obtener stream del micrófono
480
+ microphoneStream = await navigator.mediaDevices.getUserMedia({
481
+ audio: {
482
+ echoCancellation: true,
483
+ noiseSuppression: true,
484
+ autoGainControl: false
485
+ }
486
+ });
487
 
488
+ const source = audioContext.createMediaStreamSource(microphoneStream);
489
+ source.connect(highpass);
490
+ highpass.connect(lowpass);
491
+ lowpass.connect(analyser);
492
 
493
+ return true;
494
  } catch (error) {
495
+ console.error('Error configurando análisis de audio:', error);
496
+ return false;
 
 
 
 
497
  }
498
  }
499
 
500
+ // Función para detectar interrupción basada en energía de audio
501
+ function detectInterruption() {
502
+ if (!analyser) return false;
 
 
 
503
 
504
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
505
+ analyser.getByteFrequencyData(dataArray);
 
 
 
506
 
507
+ // Calcular energía promedio
508
+ const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
509
+ const normalizedEnergy = average / 128; // Normalizar a un rango de 0-1
510
+
511
+ return normalizedEnergy > ENERGY_THRESHOLD;
512
+ }
513
+
514
+ // Funciones de sesión
515
+ async function initSession() {
516
  try {
517
+ const response = await fetch('/get_session_data');
518
+ const data = await response.json();
519
+ sessionId = data.id;
520
+ currentMode = data.mode;
521
+ currentModel = data.model;
522
+ currentTTS = data.tts;
 
 
 
 
 
 
523
 
524
+ // Actualizar selectores con valores de la sesión
525
+ if (modoSelect) modoSelect.value = currentMode;
526
+ if (modeloSelect) modeloSelect.value = currentModel;
527
+ if (vozSelect) vozSelect.value = currentTTS;
528
 
529
+ // Cargar historial de chat
530
+ if (data.chat_history && data.chat_history.length > 0) {
531
+ data.chat_history.forEach(msg => {
532
+ ChatManager.addMessage(msg.text, msg.sender);
533
+ });
534
+ }
535
 
536
+ return sessionId;
537
  } catch (error) {
538
+ console.error('Error inicializando sesión:', error);
539
+ return null;
540
  }
541
  }
542
 
543
+ async function updateAudioState(isPlaying, currentAudio = null) {
544
+ try {
545
+ await fetch('/update_audio_state', {
546
+ method: 'POST',
547
+ headers: {
548
+ 'Content-Type': 'application/json'
549
+ },
550
+ body: JSON.stringify({
551
+ is_playing: isPlaying,
552
+ current_audio: currentAudio
553
+ })
554
+ });
555
+ } catch (error) {
556
+ console.error('Error actualizando estado del audio:', error);
557
+ }
558
  }
559
 
560
+ // Función para obtener el mensaje de presentación según el modo
561
+ function getMensajePresentacion(modo) {
562
+ const presentaciones = {
563
+ 'soporte': '¡Hola! Soy tu asistente de soporte técnico. Estoy aquí para ayudarte con cualquier problema de PC o Android. ¿En qué puedo ayudarte?',
564
+ 'seguros': '¡Hola! Soy tu asesor de seguros personal. Te ayudaré a encontrar la mejor protección para ti y tu familia. ¿Qué tipo de seguro te interesa?',
565
+ 'creditos': '¡Hola! Soy tu asesor financiero. Estoy aquí para ayudarte a obtener el crédito que necesitas con las mejores condiciones. ¿Cuánto necesitas?',
566
+ 'cobranza': '¡Hola! Soy tu gestor de cobranza. Estoy aquí para ayudarte a regularizar tu situación y encontrar la mejor solución para ti. ¿Cómo puedo ayudarte?',
567
+ 'encuestas': '¡Hola! Soy tu encuestador profesional. Me gustaría conocer tu opinión sobre temas importantes. ¿Estás listo para comenzar?'
568
+ };
569
+ return presentaciones[modo] || '¡Hola! ¿En qué puedo ayudarte?';
570
  }
571
 
572
+ // Función para iniciar el modo con presentación
573
+ async function iniciarModoConPresentacion(modo) {
574
  try {
575
+ Utils.updateStatus('Iniciando modo...');
576
+ const mensajePresentacion = getMensajePresentacion(modo);
577
 
578
+ const response = await fetch('/cambiar_modo', {
579
+ method: 'POST',
580
+ headers: {
581
+ 'Content-Type': 'application/json'
582
+ },
583
+ body: JSON.stringify({ mode: modo })
584
+ });
585
+
586
+ const data = await response.json();
587
+ if (data.success) {
588
+ currentMode = modo;
589
+ ChatManager.addMessage(mensajePresentacion, 'bot');
590
 
591
+ if (data.audio) {
592
+ try {
593
+ const audioType = data.audio_type || 'wav';
594
+ await AudioManager.playAudio(data.audio, audioType);
595
+ } catch (error) {
596
+ console.error('Error reproduciendo audio:', error);
597
+ handleAudioError();
598
+ }
599
  }
 
 
 
 
600
  }
601
  } catch (error) {
602
+ console.error('Error iniciando modo:', error);
603
+ Utils.updateStatus('Error al iniciar modo');
604
  }
605
  }
606
 
607
+ // Event Listeners
608
+ document.addEventListener('DOMContentLoaded', async () => {
609
+ console.log('Página cargada, inicializando...');
610
+
611
+ // Inicializar sesión
612
+ await initSession();
613
+
614
+ // Inicializar reconocimiento de voz
615
+ await VoiceManager.startRecognition();
616
+
617
+ // Iniciar modo inicial con presentación
618
+ await iniciarModoConPresentacion(currentMode);
619
+
620
+ // Iniciar reconocimiento automáticamente
621
+ setTimeout(() => {
622
+ if (startRecordingButton && !isListening && !isPlayingAudio) {
623
+ console.log('Iniciando reconocimiento automático...');
624
+ VoiceManager.startRecognition();
625
+ Utils.updateStatus('Escuchando...');
626
+ }
627
+ }, 1000);
628
+
629
+ // Botón de envío de texto
630
+ if (sendTextButton) {
631
+ sendTextButton.addEventListener('click', () => {
632
+ const text = textInput.value.trim();
633
+ if (text) {
634
+ ChatManager.sendMessage(text);
635
  }
636
+ });
637
+ }
638
+
639
+ // Input de texto (Enter)
640
+ if (textInput) {
641
+ textInput.addEventListener('keypress', (e) => {
642
+ if (e.key === 'Enter') {
643
+ const text = textInput.value.trim();
644
+ if (text) {
645
+ ChatManager.sendMessage(text);
646
+ }
647
  }
648
+ });
649
+ }
650
+
651
+ // Selectores
652
+ if (modoSelect) {
653
+ modoSelect.addEventListener('change', async function() {
654
+ try {
655
+ Utils.updateStatus('Cambiando modo...');
656
 
657
+ // Detener cualquier audio actual
658
+ if (currentAudio && isPlaying) {
659
+ currentAudio.pause();
660
+ currentAudio.currentTime = 0;
661
+ currentAudio = null;
662
+ isPlaying = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  }
664
+
665
+ // Iniciar nuevo modo con presentación
666
+ await iniciarModoConPresentacion(this.value);
667
+
668
+ } catch (error) {
669
+ console.error('Error en cambio de modo:', error);
670
+ Utils.updateStatus('Error al cambiar modo');
671
+ this.value = currentMode; // Revertir al modo anterior
672
+ } finally {
673
+ setTimeout(() => Utils.updateStatus('Escuchando...'), 2000);
674
  }
675
+ });
 
 
 
 
 
 
 
 
 
676
  }
 
677
 
678
+ if (modeloSelect) {
679
+ modeloSelect.addEventListener('change', async (e) => {
680
+ currentModel = e.target.value;
681
+ changeModel(currentModel);
682
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  }
 
684
 
685
+ if (vozSelect) {
686
+ vozSelect.addEventListener('change', async (e) => {
687
+ const newVoice = e.target.value;
688
+ console.log('Seleccionada nueva voz:', newVoice);
689
+ Utils.updateStatus('Cambiando voz...');
690
+ currentTTS = newVoice;
691
+ changeTTS(newVoice);
692
+ });
 
 
 
693
  }
694
+
695
+ // Botón de grabación
696
+ if (startRecordingButton) {
697
+ startRecordingButton.addEventListener('click', async () => {
698
+ if (!isListening) {
699
+ await VoiceManager.startRecognition();
700
+ Utils.updateStatus('Escuchando...');
701
+ } else {
702
+ await VoiceManager.pauseRecognition();
703
+ Utils.updateStatus('Reconocimiento pausado');
704
+ }
705
+ });
706
  }
707
+
708
+ // Manejar visibilidad de la página
709
+ document.addEventListener('visibilitychange', () => {
710
+ if (document.hidden) {
711
+ if (isListening) {
712
+ VoiceManager.pauseRecognition();
713
+ }
714
+ } else {
715
+ if (!isListening && !isPlayingAudio) {
716
+ setTimeout(VoiceManager.startRecognition, 500);
717
+ }
718
+ }
719
+ });
720
  });
721
+
722
+ // Agregar al inicio del archivo, después de las variables globales
723
+ const permissionModal = `
724
+ <div class="permission-modal" id="permissionModal">
725
+ <div class="permission-content">
726
+ <h4>Permisos de Micrófono</h4>
727
+ <p>Para usar el chat por voz, necesitamos acceso a tu micrófono.</p>
728
+ <div class="alert alert-warning">
729
+ <small>⚠️ Si el sitio no es seguro (HTTPS), el navegador puede bloquear el acceso al micrófono.</small>
730
+ </div>
731
+ <div class="permission-buttons">
732
+ <button class="btn btn-primary" onclick="requestMicrophonePermission()">Permitir Micrófono</button>
733
+ <button class="btn btn-secondary" onclick="closePermissionModal()">Cancelar</button>
734
+ </div>
735
+ </div>
736
+ </div>`;
737
+
738
+ // Agregar después de document.addEventListener('DOMContentLoaded'...
739
+ document.body.insertAdjacentHTML('beforeend', permissionModal);
740
+
741
+ // Funciones para el modal de permisos
742
+ function showPermissionModal() {
743
+ document.getElementById('permissionModal').classList.add('show');
744
+ }
745
+
746
+ function closePermissionModal() {
747
+ document.getElementById('permissionModal').classList.remove('show');
748
+ }
749
+
750
+ async function requestMicrophonePermission() {
751
+ try {
752
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
753
+ stream.getTracks().forEach(track => track.stop());
754
+ closePermissionModal();
755
+ await VoiceManager.initializeSpeechRecognition();
756
+ } catch (error) {
757
+ console.error('Error al solicitar permisos:', error);
758
+ Utils.updateStatus('Error: No se pudo acceder al micrófono', 'error');
759
+ ChatManager.addMessage('❌ No se pudo acceder al micrófono. Por favor, verifica los permisos en tu navegador.', 'bot');
760
+ }
761
+ }