File size: 10,891 Bytes
1cb13bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
from flask import Flask
from tts_utils import TTSUtils
from inference import InferenceManager
from huggingface_utils import HuggingFaceUtils
from flow_bot import FlowBot
from tunnel_manager import TunnelManager
from routes import Routes
from session_manager import SessionManager
from data_manager import DataManager
import yaml
import os
import webbrowser
import threading
import time
import sys
from datetime import timedelta
import logging
from colorama import init, Fore, Back, Style

# Inicializar colorama para Windows
init()

def print_startup_checklist():
    """Muestra el checklist de inicio con colores"""
    print(f"\n{Fore.CYAN}{'='*50}")
    print(f"{Fore.YELLOW}🤖 Iniciando Asistente Virtual...")
    print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}\n")

    # Checklist de Modelos
    print(f"{Fore.MAGENTA}📋 Modelos de IA:{Style.RESET_ALL}")
    print(f"{Fore.GREEN}{Fore.WHITE}Principal: {Fore.YELLOW}Gemini 8b 🧠")
    print(f"{Fore.GREEN}{Fore.WHITE}Respaldo: {Fore.YELLOW}Mixtral 7b ⚡\n")

    # Checklist de TTS
    print(f"{Fore.MAGENTA}🎤 Motores TTS:{Style.RESET_ALL}")
    print(f"{Fore.GREEN}{Fore.WHITE}EDGE (Principal)")
    print(f"  {Fore.CYAN}└─ Voz: Jorge (MX) 📢")
    print(f"{Fore.GREEN}{Fore.WHITE}EDGE_ES")
    print(f"  {Fore.CYAN}└─ Voz: Álvaro (ES) 🌐")
    print(f"{Fore.GREEN}{Fore.WHITE}VITS")
    print(f"  {Fore.CYAN}└─ Modelo Local 🔊\n")

    # Checklist de Modos
    print(f"{Fore.MAGENTA}📋 Modos Disponibles:{Style.RESET_ALL}")
    print(f"{Fore.GREEN}{Fore.WHITE}Créditos 💰")
    print(f"{Fore.GREEN}{Fore.WHITE}Seguros 🛡️")
    print(f"{Fore.GREEN}{Fore.WHITE}Cobranza 💵\n")

    # Estado del Sistema
    print(f"{Fore.MAGENTA}🔄 Estado del Sistema:{Style.RESET_ALL}")

class WebChatbotApp:
    def __init__(self):
        # Mostrar checklist inicial
        print_startup_checklist()
        
        self.flask_app = Flask(__name__)
        self.flask_app.config['SECRET_KEY'] = os.urandom(24)
        self.flask_app.config['SESSION_TYPE'] = 'filesystem'
        self.flask_app.config['SESSION_FILE_DIR'] = './flask_session'
        self.flask_app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
        self.tunnel_url = None
        
        self.load_config()
        self.init_components()
        self.routes = Routes(self.flask_app, self)
        
        # Inicializar DataManager
        self.data_manager = DataManager()
        
        print(f"{Fore.GREEN}{Fore.WHITE}Aplicación inicializada correctamente{Style.RESET_ALL}")

    def load_config(self):
        try:
            config_path = os.path.join(os.path.dirname(__file__), 'config.yaml')
            with open(config_path, 'r', encoding='utf-8') as f:
                self.config = yaml.safe_load(f)
            print(f"{Fore.GREEN}{Fore.WHITE}Configuración cargada")
        except Exception as e:
            print(f"{Fore.RED}✗ Error cargando config.yaml: {e}{Style.RESET_ALL}")
            self.config = {}
            
    def init_components(self):
        try:
            print(f"{Fore.CYAN}⚙️ Iniciando componentes...{Style.RESET_ALL}")
            
            elevenlabs_key = self.config.get('ELEVENLABS_API_KEY', '')
            huggingface_token = self.config.get('api_keys', {}).get('HUGGINGFACE_TOKEN', '')
            
            self.tts = TTSUtils(
                model_name='EDGE',
                elevenlabs_api_key=elevenlabs_key
            )
            self.inference = InferenceManager(self.config)
            self.hf_utils = HuggingFaceUtils(huggingface_token)
            self.flow_bot = FlowBot()
            self.session_manager = SessionManager()
            
            print(f"{Fore.GREEN}{Fore.WHITE}Componentes iniciados correctamente{Style.RESET_ALL}")
            
        except Exception as e:
            print(f"{Fore.RED}✗ Error iniciando componentes: {e}{Style.RESET_ALL}")
            raise

    def reinit_components(self):
        try:
            self.load_config()
            
            elevenlabs_key = self.config.get('ELEVENLABS_API_KEY', '')
            huggingface_token = self.config.get('api_keys', {}).get('HUGGINGFACE_TOKEN', '')
            
            print(f"{Fore.CYAN}⚙️ Reiniciando componentes...{Style.RESET_ALL}")
            
            self.tts = TTSUtils(
                model_name='EDGE',
                elevenlabs_api_key=elevenlabs_key
            )
            self.inference = InferenceManager(self.config)
            self.hf_utils = HuggingFaceUtils(huggingface_token)
            
            print(f"{Fore.GREEN}{Fore.WHITE}Componentes reiniciados correctamente{Style.RESET_ALL}")
            
        except Exception as e:
            print(f"{Fore.RED}✗ Error reiniciando componentes: {e}{Style.RESET_ALL}")
            raise

    def procesar_mensaje(self, mensaje):
        """Procesa un mensaje de texto y retorna la respuesta"""
        try:
            # Obtener el modo actual de la sesión
            session_id = self.session_manager.get_session()
            current_mode = self.session_manager.get_session_data(session_id, 'mode')
            
            # Verificar si estamos en proceso de recolección de datos
            if self.data_manager.is_collecting_data():
                respuesta = self.data_manager.handle_data_collection(
                    mensaje,
                    current_mode,
                    self.flow_bot.get_data_collection_steps
                )
                if respuesta:
                    return respuesta
            
            # Obtener contexto del flow_bot usando el modo actual
            contexto = self.flow_bot.get_context(current_mode, mensaje)
            
            # Obtener respuesta usando el inference manager
            respuesta = self.inference.get_response(
                prompt=mensaje,
                context=contexto
            )

            # Verificar que la respuesta no sea un mensaje de error o predeterminado
            mensajes_error = [
                "No se proporcionó un mensaje",
                "Lo siento, hubo un error",
                "Error de autenticación",
                "El servicio está ocupado",
                "No se pudo generar una respuesta coherente",
                "¡Hola! Soy tu asistente virtual"
            ]
            
            if not respuesta or any(msg in respuesta for msg in mensajes_error):
                print("Respuesta no válida del modelo, usando contexto directo")
                respuesta = self.flow_bot.get_success_message(current_mode)
                # Iniciar recolección de datos si es necesario
                if "nombre" in respuesta.lower():
                    self.data_manager.start_data_collection()
            
            return respuesta
            
        except Exception as e:
            print(f"Error procesando mensaje: {e}")
            return self.flow_bot.get_negative_response(current_mode)

    def procesar_audio(self, audio_path):
        """Procesa un archivo de audio y retorna mensaje y audio de respuesta"""
        try:
            # Transcribir audio a texto
            mensaje = self.inference.transcribe_audio(audio_path)
            print(f"Audio transcrito: {mensaje}")
            
            # Obtener respuesta
            respuesta = self.procesar_mensaje(mensaje)
            print(f"Respuesta generada: {respuesta}")
            
            # Convertir respuesta a audio
            audio_path = self.tts.text_to_speech(respuesta)
            return mensaje, audio_path
            
        except Exception as e:
            print(f"Error procesando audio: {e}")
            return "Error al procesar el audio", None

    def open_browser(self, url):
        time.sleep(2)
        webbrowser.open(url)

    def run(self, host='127.0.0.1', port=5000, debug=False, use_tunnel=True):
        try:
            if use_tunnel:
                try:
                    tunnel_manager = TunnelManager()
                    # Primero intentar obtener un túnel activo
                    self.tunnel_url = tunnel_manager.get_active_tunnel()
                    
                    if not self.tunnel_url:
                        # Si no hay túnel activo, limpiar y crear uno nuevo
                        tunnel_manager.setup_ngrok()
                        tunnel_manager.cleanup()
                        self.tunnel_url = tunnel_manager.start(port)
                    
                    if self.tunnel_url:
                        print(f"{Fore.GREEN}{Fore.WHITE}Túnel iniciado en: {Fore.CYAN}{self.tunnel_url}{Style.RESET_ALL}")
                        threading.Thread(target=self.open_browser, args=(self.tunnel_url,)).start()
                    else:
                        print(f"{Fore.YELLOW}⚠️ No se pudo iniciar el túnel, continuando en modo local{Style.RESET_ALL}")
                except Exception as tunnel_error:
                    print(f"{Fore.YELLOW}⚠️ Continuando en modo local{Style.RESET_ALL}")
            
            print(f"\n{Fore.GREEN}{Fore.WHITE}Servidor iniciado en: {Fore.CYAN}http://{host}:{port}{Style.RESET_ALL}\n")
            self.flask_app.run(host=host, port=port, debug=debug, threaded=True)
            
        except Exception as e:
            print(f"{Fore.RED}✗ Error iniciando el servidor: {e}{Style.RESET_ALL}")
            raise

    @property
    def app(self):
        return self.flask_app

if __name__ == '__main__':
    import os
    import psutil
    import socket
    
    def is_port_in_use(port):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            return s.connect_ex(('localhost', port)) == 0
            
    def kill_process_on_port(port):
        for proc in psutil.process_iter(['pid', 'name', 'connections']):
            try:
                for conn in proc.connections():
                    if conn.laddr.port == port:
                        print(f"Terminando proceso anterior en puerto {port}")
                        proc.terminate()
                        proc.wait()
                        return True
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass
        return False
    
    try:
        port = 5000
        if is_port_in_use(port):
            print(f"Puerto {port} en uso. Intentando liberar...")
            if kill_process_on_port(port):
                print("Proceso anterior terminado")
            else:
                print(f"No se pudo liberar el puerto {port}. Por favor, cierre la aplicación anterior manualmente.")
                exit(1)
        
        print("Iniciando nueva instancia de la aplicación...")
        app = WebChatbotApp()
        app.run()
        
    except Exception as e:
        print(f"Error al iniciar la aplicación: {e}")
        exit(1)