alex16052G commited on
Commit
34f9233
·
verified ·
1 Parent(s): 0728541

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -436
app.py CHANGED
@@ -1,70 +1,26 @@
1
- # chat_ai.py
2
-
3
- # ruff: noqa: E402
4
- # Above allows ruff to ignore E402: module level import not at top of file
5
-
6
- import re
7
- import tempfile
8
- import os
9
-
10
  import torch
11
- import click
12
  import gradio as gr
13
- import numpy as np
14
- import soundfile as sf
15
- import torchaudio
16
- from cached_path import cached_path
17
- from transformers import (
18
- AutoModelForCausalLM,
19
- AutoTokenizer,
20
- WhisperProcessor,
21
- WhisperForConditionalGeneration,
22
- )
23
- from num2words import num2words
24
-
25
- try:
26
- import spaces
27
- USING_SPACES = True
28
- except ImportError:
29
- USING_SPACES = False
30
-
31
- def gpu_decorator(func):
32
- if USING_SPACES:
33
- return spaces.GPU(func)
34
- else:
35
- return func
36
-
37
- from f5_tts.model import DiT
38
- from f5_tts.infer.utils_infer import (
39
- load_vocoder,
40
- load_model,
41
- preprocess_ref_audio_text,
42
- infer_process,
43
- remove_silence_for_generated_wav,
44
- save_spectrogram,
45
- )
46
-
47
- # Cargar el vocoder
48
- vocoder = load_vocoder()
49
-
50
- # Configuración y carga del modelo F5-TTS
51
- F5TTS_model_cfg = dict(dim=1024, depth=22, heads=16, ff_mult=2, text_dim=512, conv_layers=4)
52
- F5TTS_ema_model = load_model(
53
- DiT, F5TTS_model_cfg, str(cached_path("hf://jpgallegoar/F5-Spanish/model_1200000.safetensors"))
54
- )
55
 
56
  # Variables globales para el modelo de chat
57
  chat_model_state = None
58
  chat_tokenizer_state = None
59
 
60
- # Cargar el modelo Whisper para transcripción
61
- whisper_processor = WhisperProcessor.from_pretrained("openai/whisper-base")
62
- whisper_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-base")
63
- whisper_model.eval()
64
- if torch.cuda.is_available():
65
- whisper_model.to("cuda")
 
 
 
 
 
 
 
 
66
 
67
- @gpu_decorator
68
  def generate_response(messages, model, tokenizer):
69
  """Genera una respuesta usando el modelo de chat"""
70
  try:
@@ -100,397 +56,68 @@ def generate_response(messages, model, tokenizer):
100
 
101
  # Extraer solo la respuesta del asistente
102
  response = generated_text[len(prompt):].strip()
103
- # Opcional: Cortar la respuesta al primer salto de línea
104
- response = response.split("\n")[0]
105
  return response
106
  except Exception as e:
107
- # Log del error para depuración
108
  print(f"Error en generate_response: {e}")
109
  return "Lo siento, ocurrió un error al generar la respuesta."
110
 
111
- def traducir_numero_a_texto(texto):
112
- """Convierte números en texto a su representación en palabras en español"""
113
- texto_separado = re.sub(r'([A-Za-z])(\d)', r'\1 \2', texto)
114
- texto_separado = re.sub(r'(\d)([A-Za-z])', r'\1 \2', texto_separado)
115
-
116
- def reemplazar_numero(match):
117
- numero = match.group()
118
- try:
119
- return num2words(int(numero), lang='es')
120
- except ValueError:
121
- return numero
122
-
123
- texto_traducido = re.sub(r'\b\d+\b', reemplazar_numero, texto_separado)
124
-
125
- return texto_traducido
126
-
127
- @gpu_decorator
128
- def infer(
129
- ref_audio_orig, ref_text, gen_text, model, remove_silence, cross_fade_duration=0.15, speed=1
130
- ):
131
- """Genera el audio sintetizado a partir del texto"""
132
- try:
133
- ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_orig, ref_text)
134
-
135
- ema_model = F5TTS_ema_model
136
-
137
- if not gen_text.startswith(" "):
138
- gen_text = " " + gen_text
139
- if not gen_text.endswith(". "):
140
- gen_text += ". "
141
-
142
- gen_text = gen_text.lower()
143
- gen_text = traducir_numero_a_texto(gen_text)
144
-
145
- final_wave, final_sample_rate, combined_spectrogram = infer_process(
146
- ref_audio,
147
- ref_text,
148
- gen_text,
149
- ema_model,
150
- vocoder,
151
- cross_fade_duration=cross_fade_duration,
152
- speed=speed,
153
- progress=gr.Progress(),
154
- )
155
-
156
- # Eliminar silencios si está activado
157
- if remove_silence:
158
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
159
- sf.write(f.name, final_wave, final_sample_rate)
160
- remove_silence_for_generated_wav(f.name)
161
- final_wave, _ = torchaudio.load(f.name)
162
- final_wave = final_wave.squeeze().cpu().numpy()
163
-
164
- # Guardar el espectrograma (opcional)
165
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_spectrogram:
166
- spectrogram_path = tmp_spectrogram.name
167
- save_spectrogram(combined_spectrogram, spectrogram_path)
168
-
169
- return (final_sample_rate, final_wave), spectrogram_path
170
- except Exception as e:
171
- # Log del error para depuración
172
- print(f"Error en infer: {e}")
173
- return None, None
174
-
175
- def load_chat_model_function():
176
- """Función para cargar el modelo de chat"""
177
- global chat_model_state, chat_tokenizer_state
178
- if chat_model_state is None:
179
- try:
180
- model_name = "Qwen/Qwen2.5-3B-Instruct"
181
- chat_model_state = AutoModelForCausalLM.from_pretrained(
182
- model_name, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, device_map="auto" if torch.cuda.is_available() else None
183
- )
184
- chat_tokenizer_state = AutoTokenizer.from_pretrained(model_name)
185
- return gr.update(visible=False), gr.update(visible=True)
186
- except Exception as e:
187
- print(f"Error al cargar el modelo de chat: {e}")
188
- return gr.update(value="Error al cargar el modelo de chat."), gr.update(visible=False)
189
- else:
190
- return gr.update(visible=False), gr.update(visible=True)
191
-
192
- def transcribe_audio(audio_path):
193
- """Transcribe el audio usando el modelo Whisper"""
194
- try:
195
- if not os.path.exists(audio_path):
196
- raise FileNotFoundError(f"Archivo de audio no encontrado: {audio_path}")
197
-
198
- # Cargar el audio
199
- audio, rate = torchaudio.load(audio_path)
200
-
201
- # Resample si es necesario
202
- if rate != 16000:
203
- resampler = torchaudio.transforms.Resample(orig_freq=rate, new_freq=16000)
204
- audio = resampler(audio)
205
-
206
- # Asegurarse de que el audio tenga una sola dimensión
207
- if audio.ndim > 1:
208
- audio = torch.mean(audio, dim=0)
209
-
210
- input_features = whisper_processor(audio.cpu().numpy(), sampling_rate=16000, return_tensors="pt").input_features
211
- if torch.cuda.is_available():
212
- input_features = input_features.to("cuda")
213
-
214
- # Generar la transcripción
215
- predicted_ids = whisper_model.generate(input_features)
216
- transcription = whisper_processor.decode(predicted_ids[0], skip_special_tokens=True)
217
-
218
- return transcription
219
- except Exception as e:
220
- print(f"Error en transcribe_audio: {e}")
221
- return None
222
-
223
  with gr.Blocks() as app_chat:
224
- gr.Markdown(
225
- """
226
- # Chat de Voz
227
- ¡Mantén una conversación con una IA usando tu voz de referencia!
228
- 1. Sube un clip de audio de referencia y opcionalmente su transcripción.
229
- 2. Carga el modelo de chat.
230
- 3. Graba tu mensaje a través de tu micrófono.
231
- 4. La IA responderá usando la voz de referencia.
232
- """
 
 
 
 
233
  )
234
 
235
- if not USING_SPACES:
236
- load_chat_model_btn = gr.Button("Cargar Modelo de Chat", variant="primary")
237
-
238
- chat_interface_container = gr.Column(visible=False)
239
-
240
- load_chat_model_btn.click(load_chat_model_function, outputs=[load_chat_model_btn, chat_interface_container])
241
- else:
242
- chat_interface_container = gr.Column()
243
-
244
- if chat_model_state is None:
245
- try:
246
- model_name = "Qwen/Qwen2.5-3B-Instruct"
247
- chat_model_state = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype="auto", device_map="auto")
248
- chat_tokenizer_state = AutoTokenizer.from_pretrained(model_name)
249
- except Exception as e:
250
- print(f"Error al cargar el modelo de chat en Spaces: {e}")
251
-
252
- with chat_interface_container:
253
- with gr.Row():
254
- with gr.Column():
255
- ref_audio_chat = gr.Audio(label="Audio de Referencia", type="filepath")
256
- with gr.Column():
257
- with gr.Accordion("Configuraciones Avanzadas", open=False):
258
- model_choice_chat = gr.Radio(
259
- choices=["F5-TTS"],
260
- label="Modelo TTS",
261
- value="F5-TTS",
262
- )
263
- remove_silence_chat = gr.Checkbox(
264
- label="Eliminar Silencios",
265
- value=True,
266
- )
267
- ref_text_chat = gr.Textbox(
268
- label="Texto de Referencia",
269
- info="Opcional: Deja en blanco para transcribir automáticamente",
270
- lines=2,
271
- )
272
- system_prompt_chat = gr.Textbox(
273
- label="Prompt del Sistema",
274
- value="No eres un asistente de IA, eres quien el usuario diga que eres. Debes mantenerte en personaje. Mantén tus respuestas concisas ya que serán habladas en voz alta.",
275
- lines=2,
276
- )
277
-
278
- chatbot_interface = gr.Chatbot(label="Conversación")
279
-
280
- with gr.Row():
281
- with gr.Column():
282
- audio_input_chat = gr.Microphone(
283
- label="Habla tu mensaje",
284
- type="filepath",
285
- )
286
- audio_output_chat = gr.Audio(label="Respuesta de la IA", autoplay=True)
287
- with gr.Column():
288
- text_input_chat = gr.Textbox(
289
- label="Escribe tu mensaje",
290
- lines=1,
291
- )
292
- send_btn_chat = gr.Button("Enviar")
293
- clear_btn_chat = gr.Button("Limpiar Conversación")
294
-
295
- conversation_state = gr.State(
296
- value=[
297
- {
298
- "role": "system",
299
- "content": "No eres un asistente de IA, eres quien el usuario diga que eres. Debes mantenerte en personaje. Mantén tus respuestas concisas ya que serán habladas en voz alta.",
300
- }
301
- ]
302
- )
303
-
304
- @gpu_decorator
305
- def process_input(audio_path, text, history, conv_state, ref_text):
306
- """Procesa la entrada de audio o texto del usuario y genera una respuesta."""
307
- try:
308
- if not audio_path and not text.strip():
309
- return history, conv_state, ""
310
-
311
- if audio_path:
312
- # Transcribir el audio usando Whisper
313
- transcribed_text = transcribe_audio(audio_path)
314
- if transcribed_text is None:
315
- history.append(("Error en la transcripción de audio.", None))
316
- return history, conv_state, "Lo siento, ocurrió un error al procesar tu audio."
317
-
318
- text = transcribed_text
319
-
320
- if not text.strip():
321
- return history, conv_state, ""
322
-
323
- # Si se proporciona texto de referencia, usarlo; de lo contrario, usar transcripción
324
- if ref_text.strip():
325
- input_text = ref_text + " " + text
326
- else:
327
- input_text = text
328
-
329
- conv_state.append({"role": "user", "content": input_text})
330
- history.append((input_text, None))
331
-
332
- # Generar la respuesta del modelo de chat
333
- response = generate_response(conv_state, chat_model_state, chat_tokenizer_state)
334
-
335
- conv_state.append({"role": "assistant", "content": response})
336
- history[-1] = (input_text, response)
337
-
338
- return history, conv_state, response
339
- except Exception as e:
340
- print(f"Error en process_input: {e}")
341
- history.append(("Error al procesar tu solicitud.", None))
342
- return history, conv_state, "Lo siento, ocurrió un error al procesar tu solicitud."
343
-
344
- @gpu_decorator
345
- def generate_audio_response(response, ref_audio, ref_text, model, remove_silence):
346
- """Genera el audio de respuesta para la IA."""
347
- try:
348
- if not response or not ref_audio:
349
- return None
350
-
351
- # Verificar si la respuesta es un mensaje de error
352
- if response.startswith("Lo siento"):
353
- # Aquí podrías tener un audio pregrabado de error
354
- # Por ejemplo, "error_audio.wav" en el mismo directorio
355
- error_audio_path = "error_audio.wav"
356
- if os.path.exists(error_audio_path):
357
- return error_audio_path
358
- else:
359
- # Si no tienes un archivo de audio de error, puedes retornar None
360
- # O generar el audio dinámicamente usando infer
361
- audio_result, _ = infer(
362
- ref_audio,
363
- ref_text,
364
- response,
365
- model,
366
- remove_silence,
367
- cross_fade_duration=0.15,
368
- speed=1.0,
369
- )
370
- return audio_result
371
- else:
372
- # Generar el audio de la respuesta normal
373
- audio_result, _ = infer(
374
- ref_audio,
375
- ref_text,
376
- response,
377
- model,
378
- remove_silence,
379
- cross_fade_duration=0.15,
380
- speed=1.0,
381
- )
382
- if audio_result is None:
383
- # Retornar un audio de error si infer falla
384
- error_audio_path = "error_audio.wav"
385
- if os.path.exists(error_audio_path):
386
- return error_audio_path
387
- else:
388
- return None
389
- return audio_result
390
- except Exception as e:
391
- print(f"Error en generate_audio_response: {e}")
392
- # Retornar un audio de error si ocurre una excepción
393
- error_audio_path = "error_audio.wav"
394
- if os.path.exists(error_audio_path):
395
- return error_audio_path
396
- else:
397
- return None
398
 
399
- def clear_conversation():
400
- """Reset the conversation"""
401
- return [], [
402
- {
403
- "role": "system",
404
- "content": "No eres un asistente de IA, eres quien el usuario diga que eres. Debes mantenerte en personaje. Mantén tus respuestas concisas ya que serán habladas en voz alta.",
405
- }
406
- ]
407
 
408
- def update_system_prompt(new_prompt):
409
- """Update the system prompt and reset the conversation"""
410
- new_conv_state = [{"role": "system", "content": new_prompt}]
411
- return [], new_conv_state
412
 
413
- # Manejar la entrada de audio
414
- audio_input_chat.change(
415
- process_input,
416
- inputs=[audio_input_chat, text_input_chat, chatbot_interface, conversation_state, ref_text_chat],
417
- outputs=[chatbot_interface, conversation_state, text_input_chat],
418
- ).then(
419
- generate_audio_response,
420
- inputs=[chatbot_interface, ref_audio_chat, ref_text_chat, model_choice_chat, remove_silence_chat],
421
- outputs=[audio_output_chat],
422
- ).then(
423
- lambda: None,
424
- None,
425
- audio_input_chat,
426
- )
427
 
428
- # Manejar la entrada de texto
429
- text_input_chat.submit(
430
- process_input,
431
- inputs=[audio_input_chat, text_input_chat, chatbot_interface, conversation_state, ref_text_chat],
432
- outputs=[chatbot_interface, conversation_state, text_input_chat],
433
- ).then(
434
- generate_audio_response,
435
- inputs=[chatbot_interface, ref_audio_chat, ref_text_chat, model_choice_chat, remove_silence_chat],
436
- outputs=[audio_output_chat],
437
- ).then(
438
- lambda: None,
439
- None,
440
- text_input_chat,
441
- )
442
 
443
- # Manejar el botón de enviar
444
- send_btn_chat.click(
445
- process_input,
446
- inputs=[audio_input_chat, text_input_chat, chatbot_interface, conversation_state, ref_text_chat],
447
- outputs=[chatbot_interface, conversation_state, text_input_chat],
448
- ).then(
449
- generate_audio_response,
450
- inputs=[chatbot_interface, ref_audio_chat, ref_text_chat, model_choice_chat, remove_silence_chat],
451
- outputs=[audio_output_chat],
452
- ).then(
453
- lambda: None,
454
- None,
455
- text_input_chat,
456
- )
457
-
458
- # Manejar el botón de limpiar conversación
459
- clear_btn_chat.click(
460
- clear_conversation,
461
- outputs=[chatbot_interface, conversation_state],
462
- )
463
 
464
- # Manejar cambios en el prompt del sistema
465
- system_prompt_chat.change(
466
- update_system_prompt,
467
- inputs=system_prompt_chat,
468
- outputs=[chatbot_interface, conversation_state],
469
- )
470
 
471
- @click.command()
472
- @click.option("--port", "-p", default=None, type=int, help="Puerto para ejecutar la aplicación")
473
- @click.option("--host", "-H", default=None, help="Host para ejecutar la aplicación")
474
- @click.option(
475
- "--share",
476
- "-s",
477
- default=False,
478
- is_flag=True,
479
- help="Compartir la aplicación a través de un enlace compartido de Gradio",
480
- )
481
- @click.option("--api", "-a", default=True, is_flag=True, help="Permitir acceso a la API")
482
- def main(port, host, share, api):
483
- """Función principal para lanzar la aplicación Gradio de Chat AI."""
484
- print("Iniciando la aplicación de Chat AI...")
485
- app_chat.queue(api_open=api).launch(
486
- server_name=host,
487
- server_port=port,
488
- share=share,
489
- show_api=api
490
  )
491
 
492
- if __name__ == "__main__":
493
- if not USING_SPACES:
494
- main()
495
- else:
496
- app_chat.queue().launch(share=True) # Asegura que 'share=True' si se usa Spaces
 
 
 
 
 
 
 
 
 
 
1
  import torch
2
+ from transformers import AutoModelForCausalLM, AutoTokenizer
3
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  # Variables globales para el modelo de chat
6
  chat_model_state = None
7
  chat_tokenizer_state = None
8
 
9
+ def load_chat_model():
10
+ """Función para cargar el modelo de chat"""
11
+ global chat_model_state, chat_tokenizer_state
12
+ try:
13
+ model_name = "Qwen/Qwen2.5-3B-Instruct"
14
+ chat_model_state = AutoModelForCausalLM.from_pretrained(
15
+ model_name,
16
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
17
+ device_map="auto" if torch.cuda.is_available() else None
18
+ )
19
+ chat_tokenizer_state = AutoTokenizer.from_pretrained(model_name)
20
+ print("Modelo cargado exitosamente.")
21
+ except Exception as e:
22
+ print(f"Error al cargar el modelo de chat: {e}")
23
 
 
24
  def generate_response(messages, model, tokenizer):
25
  """Genera una respuesta usando el modelo de chat"""
26
  try:
 
56
 
57
  # Extraer solo la respuesta del asistente
58
  response = generated_text[len(prompt):].strip()
 
 
59
  return response
60
  except Exception as e:
 
61
  print(f"Error en generate_response: {e}")
62
  return "Lo siento, ocurrió un error al generar la respuesta."
63
 
64
+ # Gradio Interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  with gr.Blocks() as app_chat:
66
+ gr.Markdown("### Chatbot Simple")
67
+ chatbot_interface = gr.Chatbot(label="Conversación")
68
+ text_input_chat = gr.Textbox(label="Escribe tu mensaje", lines=1)
69
+ send_btn_chat = gr.Button("Enviar")
70
+ clear_btn_chat = gr.Button("Limpiar Conversación")
71
+
72
+ conversation_state = gr.State(
73
+ value=[
74
+ {
75
+ "role": "system",
76
+ "content": "Eres un chatbot. Responde a las preguntas del usuario de manera concisa y clara.",
77
+ }
78
+ ]
79
  )
80
 
81
+ def process_input(text, history, conv_state):
82
+ """Procesa la entrada de texto del usuario y genera una respuesta."""
83
+ if not text.strip():
84
+ return history, conv_state, ""
85
+
86
+ conv_state.append({"role": "user", "content": text})
87
+ history.append((text, None))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ # Generar la respuesta del modelo de chat
90
+ response = generate_response(conv_state, chat_model_state, chat_tokenizer_state)
 
 
 
 
 
 
91
 
92
+ conv_state.append({"role": "assistant", "content": response})
93
+ history[-1] = (text, response)
 
 
94
 
95
+ return history, conv_state, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ def clear_conversation():
98
+ """Resetea la conversación"""
99
+ return [], [{"role": "system", "content": "Eres un chatbot. Responde a las preguntas del usuario de manera concisa y clara."}]
 
 
 
 
 
 
 
 
 
 
 
100
 
101
+ # Manejar entrada de texto y botones
102
+ text_input_chat.submit(
103
+ process_input,
104
+ inputs=[text_input_chat, chatbot_interface, conversation_state],
105
+ outputs=[chatbot_interface, conversation_state, text_input_chat],
106
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ send_btn_chat.click(
109
+ process_input,
110
+ inputs=[text_input_chat, chatbot_interface, conversation_state],
111
+ outputs=[chatbot_interface, conversation_state, text_input_chat],
112
+ )
 
113
 
114
+ clear_btn_chat.click(
115
+ clear_conversation,
116
+ outputs=[chatbot_interface, conversation_state],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  )
118
 
119
+ # Cargar el modelo al iniciar
120
+ load_chat_model()
121
+
122
+ # Ejecutar la aplicación
123
+ app_chat.launch()