Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,122 +16,105 @@ import time
|
|
| 16 |
import numpy as np
|
| 17 |
import wave
|
| 18 |
|
| 19 |
-
#
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 28 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 29 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 30 |
# ============= GENERAR AUDIO CON EMOCIÓN Y ANÁLISIS DE SENTIMIENTO =============
|
| 31 |
-
# ============= GENERAR AUDIO CON EMOCIÓN - VERSIÓN CORREGIDA =============
|
| 32 |
def generar_audio_respuesta(texto, client):
|
| 33 |
-
"""TTS emocional FUNCIONAL
|
| 34 |
-
|
| 35 |
-
try:
|
| 36 |
-
# Limpiar y preparar texto
|
| 37 |
-
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 38 |
-
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 39 |
-
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 40 |
-
texto_audio = ". ".join(oraciones[:5]) + "." if len(oraciones) > 5 else ". ".join(oraciones) + "."
|
| 41 |
-
|
| 42 |
-
if len(texto_audio) > 500:
|
| 43 |
-
texto_audio = texto_audio[:497] + "..."
|
| 44 |
-
|
| 45 |
-
print(f"🎤 Generando audio para: '{texto_audio[:100]}...'")
|
| 46 |
-
|
| 47 |
-
# PASO 1: Análisis emocional
|
| 48 |
-
emocion_detectada = "neutral"
|
| 49 |
-
confianza = 0.5
|
| 50 |
-
|
| 51 |
-
try:
|
| 52 |
-
print("🧠 Analizando emoción...")
|
| 53 |
-
emotion_response = client.text_classification(
|
| 54 |
-
text=texto_audio[:512],
|
| 55 |
-
model="finiteautomata/beto-sentiment-analysis"
|
| 56 |
-
)
|
| 57 |
-
if emotion_response and len(emotion_response) > 0:
|
| 58 |
-
label = emotion_response[0]['label'].lower()
|
| 59 |
-
sentiment_to_emotion = {
|
| 60 |
-
'pos': 'joy',
|
| 61 |
-
'positive': 'joy',
|
| 62 |
-
'neu': 'neutral',
|
| 63 |
-
'neutral': 'neutral',
|
| 64 |
-
'neg': 'sadness',
|
| 65 |
-
'negative': 'sadness'
|
| 66 |
-
}
|
| 67 |
-
emocion_detectada = sentiment_to_emotion.get(label, 'neutral')
|
| 68 |
-
confianza = emotion_response[0]['score']
|
| 69 |
-
print(f"😊 Emoción: {emocion_detectada} (confianza: {confianza:.2%})")
|
| 70 |
-
except Exception as e:
|
| 71 |
-
print(f"⚠️ Error en análisis emocional: {str(e)[:100]}")
|
| 72 |
-
|
| 73 |
-
# PASO 2: Generar audio con gTTS
|
| 74 |
-
print("🔊 Generando audio con Google TTS...")
|
| 75 |
-
|
| 76 |
-
if GTTS_AVAILABLE:
|
| 77 |
-
tts = gTTS(text=texto_audio, lang='es', slow=False)
|
| 78 |
-
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 79 |
-
audio_path = f"audio_emocional_{emocion_detectada}_{timestamp}.mp3"
|
| 80 |
-
tts.save(audio_path)
|
| 81 |
-
|
| 82 |
-
if os.path.exists(audio_path) and os.path.getsize(audio_path) > 1000:
|
| 83 |
-
print(f"✅ Audio generado: {audio_path} ({os.path.getsize(audio_path)} bytes)")
|
| 84 |
-
return audio_path, emocion_detectada, confianza
|
| 85 |
-
|
| 86 |
-
print("⚠️ Intentando método alternativo...")
|
| 87 |
-
return generar_audio_alternativo(texto, client)
|
| 88 |
-
|
| 89 |
-
except Exception as e:
|
| 90 |
-
print(f"❌ Error general: {str(e)}")
|
| 91 |
-
return None, "neutral", 0.5
|
| 92 |
-
|
| 93 |
-
def generar_audio_alternativo(texto, client):
|
| 94 |
-
"""Método alternativo usando HuggingFace TTS"""
|
| 95 |
-
emocion_detectada = "neutral"
|
| 96 |
-
confianza = 0.5
|
| 97 |
|
|
|
|
| 98 |
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 99 |
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 100 |
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 101 |
-
texto_audio = ". ".join(oraciones[:3]) + "."
|
| 102 |
-
|
| 103 |
if len(texto_audio) > 400:
|
| 104 |
texto_audio = texto_audio[:397] + "..."
|
| 105 |
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
for modelo in modelos_tts:
|
| 109 |
try:
|
| 110 |
print(f"🔊 Probando: {modelo}")
|
| 111 |
-
audio_data = client.text_to_speech(text=texto_audio, model=modelo)
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 114 |
-
audio_path = f"
|
| 115 |
|
| 116 |
with open(audio_path, "wb") as f:
|
| 117 |
if isinstance(audio_data, bytes):
|
| 118 |
f.write(audio_data)
|
| 119 |
elif hasattr(audio_data, 'read'):
|
| 120 |
f.write(audio_data.read())
|
|
|
|
|
|
|
| 121 |
else:
|
|
|
|
| 122 |
for chunk in audio_data:
|
| 123 |
if chunk:
|
| 124 |
f.write(chunk if isinstance(chunk, bytes) else bytes(chunk))
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
os.remove(audio_path)
|
|
|
|
| 132 |
except Exception as e:
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
|
|
|
|
| 135 |
return None, emocion_detectada, confianza
|
| 136 |
|
| 137 |
# ============= ASISTENTE IA CONVERSACIONAL =============
|
|
@@ -213,16 +196,12 @@ Responde ahora:"""
|
|
| 213 |
f.write(f"\nArchivo de audio: {audio_path if audio_path else 'No generado'}\n")
|
| 214 |
f.write("=" * 60 + "\n")
|
| 215 |
|
| 216 |
-
if audio_path
|
| 217 |
print(f"✅ Audio generado correctamente: {audio_path}")
|
| 218 |
-
return respuesta, audio_path, transcripcion_path, emocion, confianza
|
| 219 |
else:
|
| 220 |
print("⚠️ No se pudo generar el audio, pero la respuesta está disponible")
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
with open(audio_vacio, "w") as f:
|
| 224 |
-
f.write("")
|
| 225 |
-
return respuesta, audio_vacio, transcripcion_path, emocion, confianza
|
| 226 |
|
| 227 |
except Exception as e:
|
| 228 |
print(f"❌ Error con {modelo}: {str(e)}")
|
|
@@ -1551,7 +1530,7 @@ with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
|
|
| 1551 |
label="🎧 Reproducir respuesta en audio",
|
| 1552 |
type="filepath",
|
| 1553 |
visible=True,
|
| 1554 |
-
autoplay=
|
| 1555 |
)
|
| 1556 |
with gr.Column():
|
| 1557 |
transcripcion_output = gr.File(
|
|
@@ -1772,56 +1751,42 @@ with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
|
|
| 1772 |
outputs=[pdf_output, pdf_status]
|
| 1773 |
)
|
| 1774 |
|
| 1775 |
-
|
| 1776 |
def consultar_ia_con_loading(texto, pregunta):
|
| 1777 |
-
|
| 1778 |
-
|
| 1779 |
-
|
| 1780 |
-
|
| 1781 |
-
|
| 1782 |
-
|
| 1783 |
-
|
| 1784 |
-
|
| 1785 |
-
|
| 1786 |
-
|
| 1787 |
-
|
| 1788 |
-
|
| 1789 |
-
|
| 1790 |
-
|
| 1791 |
-
|
| 1792 |
-
|
| 1793 |
-
|
| 1794 |
-
|
| 1795 |
-
|
| 1796 |
-
|
| 1797 |
-
|
| 1798 |
-
|
| 1799 |
-
|
| 1800 |
-
|
| 1801 |
-
<
|
| 1802 |
-
<
|
| 1803 |
-
|
| 1804 |
-
|
| 1805 |
-
|
| 1806 |
-
|
| 1807 |
-
|
| 1808 |
-
|
| 1809 |
-
|
| 1810 |
-
|
| 1811 |
-
audio_final = audio if (audio and os.path.exists(audio) and os.path.getsize(audio) > 100) else None
|
| 1812 |
-
|
| 1813 |
-
if audio_final:
|
| 1814 |
-
print(f"✅ Audio disponible: {audio_final}")
|
| 1815 |
-
else:
|
| 1816 |
-
print("⚠️ Audio no disponible")
|
| 1817 |
-
emocion_info += "\n\n⚠️ *El audio no pudo generarse, pero la respuesta está en texto.*"
|
| 1818 |
-
|
| 1819 |
-
yield (respuesta, audio_final, transcripcion, emocion_info, gr.update(visible=False))
|
| 1820 |
-
|
| 1821 |
-
except Exception as e:
|
| 1822 |
-
error_msg = f"❌ Error: {str(e)[:200]}"
|
| 1823 |
-
print(f"Error completo: {str(e)}")
|
| 1824 |
-
yield (error_msg, None, None, "", gr.update(visible=False))
|
| 1825 |
|
| 1826 |
btn_consulta_ia.click(
|
| 1827 |
fn=consultar_ia_con_loading,
|
|
|
|
| 16 |
import numpy as np
|
| 17 |
import wave
|
| 18 |
|
| 19 |
+
# ============= EXTRAER TEXTO DEL PDF =============
|
| 20 |
+
def extraer_texto_pdf(pdf_file):
|
| 21 |
+
try:
|
| 22 |
+
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
| 23 |
+
texto = ""
|
| 24 |
+
for pagina in pdf_reader.pages:
|
| 25 |
+
texto += pagina.extract_text() + "\n"
|
| 26 |
+
return texto
|
| 27 |
+
except Exception as e:
|
| 28 |
+
return f"Error: {str(e)}"
|
| 29 |
|
| 30 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 31 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 32 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 33 |
# ============= GENERAR AUDIO CON EMOCIÓN Y ANÁLISIS DE SENTIMIENTO =============
|
|
|
|
| 34 |
def generar_audio_respuesta(texto, client):
|
| 35 |
+
"""TTS emocional FUNCIONAL para español - Actualizado diciembre 2025"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
# Limpiar y preparar texto (mismo que antes)
|
| 38 |
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 39 |
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 40 |
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 41 |
+
texto_audio = ". ".join(oraciones[:3]) + "." if len(oraciones) > 3 else ". ".join(oraciones) + "."
|
|
|
|
| 42 |
if len(texto_audio) > 400:
|
| 43 |
texto_audio = texto_audio[:397] + "..."
|
| 44 |
|
| 45 |
+
print(f"🎤 Generando audio para: '{texto_audio[:80]}...'")
|
| 46 |
+
|
| 47 |
+
# PASO 1: Análisis emocional (modelo español que SÍ funciona)
|
| 48 |
+
try:
|
| 49 |
+
print("🧠 Analizando emoción...")
|
| 50 |
+
emotion_response = client.text_classification(
|
| 51 |
+
text=texto_audio,
|
| 52 |
+
model="dariolopez/roberta-base-bne-finetuned-EmotionAnalysisSpanish" # Español nativo
|
| 53 |
+
)
|
| 54 |
+
if emotion_response and len(emotion_response) > 0:
|
| 55 |
+
emocion_detectada = emotion_response[0]['label']
|
| 56 |
+
confianza = emotion_response[0]['score']
|
| 57 |
+
print(f"😊 Emoción: {emocion_detectada} (confianza: {confianza:.2%})")
|
| 58 |
+
else:
|
| 59 |
+
emocion_detectada = "neutral"
|
| 60 |
+
confianza = 0.5
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f"⚠️ Error emocional: {str(e)[:100]}. Usando neutral.")
|
| 63 |
+
emocion_detectada = "neutral"
|
| 64 |
+
confianza = 0.5
|
| 65 |
+
|
| 66 |
+
# PASO 2: Modelos TTS que SÍ funcionan en 2025 (español prioritario)
|
| 67 |
+
modelos_tts = [
|
| 68 |
+
"facebook/mms-tts-spa", # Español oficial de Meta - Siempre funciona
|
| 69 |
+
"myshell-ai/MeloTTS-Spanish", # Alta calidad, multi-idioma
|
| 70 |
+
"coqui/XTTS-v2" # Fallback versátil (soporta español)
|
| 71 |
+
]
|
| 72 |
|
| 73 |
for modelo in modelos_tts:
|
| 74 |
try:
|
| 75 |
print(f"🔊 Probando: {modelo}")
|
|
|
|
| 76 |
|
| 77 |
+
# Generar audio
|
| 78 |
+
audio_data = client.text_to_speech(
|
| 79 |
+
text=texto_audio,
|
| 80 |
+
model=modelo
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# Guardar archivo (mejorado para streams/bytes)
|
| 84 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 85 |
+
audio_path = f"audio_emocional_{emocion_detectada}_{timestamp}.wav"
|
| 86 |
|
| 87 |
with open(audio_path, "wb") as f:
|
| 88 |
if isinstance(audio_data, bytes):
|
| 89 |
f.write(audio_data)
|
| 90 |
elif hasattr(audio_data, 'read'):
|
| 91 |
f.write(audio_data.read())
|
| 92 |
+
elif hasattr(audio_data, 'content'):
|
| 93 |
+
f.write(audio_data.content)
|
| 94 |
else:
|
| 95 |
+
# Para iteradores/chunks
|
| 96 |
for chunk in audio_data:
|
| 97 |
if chunk:
|
| 98 |
f.write(chunk if isinstance(chunk, bytes) else bytes(chunk))
|
| 99 |
|
| 100 |
+
# Verificar
|
| 101 |
+
if os.path.exists(audio_path):
|
| 102 |
+
size = os.path.getsize(audio_path)
|
| 103 |
+
print(f"📁 Creado: {audio_path} ({size} bytes)")
|
| 104 |
+
|
| 105 |
+
if size > 2000: # Umbral más bajo para MMS
|
| 106 |
+
print(f"✅ ¡AUDIO GENERADO EXITOSAMENTE!")
|
| 107 |
+
return audio_path, emocion_detectada, confianza
|
| 108 |
+
else:
|
| 109 |
+
print(f"⚠️ Archivo pequeño ({size} bytes), borrando...")
|
| 110 |
os.remove(audio_path)
|
| 111 |
+
|
| 112 |
except Exception as e:
|
| 113 |
+
error_msg = str(e)
|
| 114 |
+
print(f"❌ Error con {modelo}: {error_msg[:100]}")
|
| 115 |
+
continue
|
| 116 |
|
| 117 |
+
print("⚠️ No se generó audio. Verifica límites de API o conexión.")
|
| 118 |
return None, emocion_detectada, confianza
|
| 119 |
|
| 120 |
# ============= ASISTENTE IA CONVERSACIONAL =============
|
|
|
|
| 196 |
f.write(f"\nArchivo de audio: {audio_path if audio_path else 'No generado'}\n")
|
| 197 |
f.write("=" * 60 + "\n")
|
| 198 |
|
| 199 |
+
if audio_path:
|
| 200 |
print(f"✅ Audio generado correctamente: {audio_path}")
|
|
|
|
| 201 |
else:
|
| 202 |
print("⚠️ No se pudo generar el audio, pero la respuesta está disponible")
|
| 203 |
+
|
| 204 |
+
return respuesta, audio_path, transcripcion_path, emocion, confianza
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
except Exception as e:
|
| 207 |
print(f"❌ Error con {modelo}: {str(e)}")
|
|
|
|
| 1530 |
label="🎧 Reproducir respuesta en audio",
|
| 1531 |
type="filepath",
|
| 1532 |
visible=True,
|
| 1533 |
+
autoplay=False
|
| 1534 |
)
|
| 1535 |
with gr.Column():
|
| 1536 |
transcripcion_output = gr.File(
|
|
|
|
| 1751 |
outputs=[pdf_output, pdf_status]
|
| 1752 |
)
|
| 1753 |
|
| 1754 |
+
# Asistente IA con análisis emocional
|
| 1755 |
def consultar_ia_con_loading(texto, pregunta):
|
| 1756 |
+
if not texto:
|
| 1757 |
+
return ("❌ Por favor, procesa una factura primero", None, None, "", gr.update(visible=False))
|
| 1758 |
+
|
| 1759 |
+
yield ("🔄 El asistente está analizando tu pregunta...", None, None, "", gr.update(visible=True))
|
| 1760 |
+
time.sleep(0.3)
|
| 1761 |
+
respuesta, audio, transcripcion, emocion, confianza = asistente_ia_factura(texto, pregunta)
|
| 1762 |
+
|
| 1763 |
+
# Mapeo de emociones a emojis y colores
|
| 1764 |
+
emotion_map = {
|
| 1765 |
+
"joy": ("😊", "#4CAF50", "Alegría"),
|
| 1766 |
+
"excitement": ("🎉", "#FF9800", "Emoción"),
|
| 1767 |
+
"anger": ("😠", "#F44336", "Enfado"),
|
| 1768 |
+
"sadness": ("😢", "#2196F3", "Tristeza"),
|
| 1769 |
+
"fear": ("😰", "#9C27B0", "Miedo"),
|
| 1770 |
+
"surprise": ("😮", "#FF5722", "Sorpresa"),
|
| 1771 |
+
"neutral": ("😐", "#607D8B", "Neutral")
|
| 1772 |
+
}
|
| 1773 |
+
|
| 1774 |
+
emoji, color, nombre = emotion_map.get(emocion, ("😐", "#607D8B", "Neutral"))
|
| 1775 |
+
|
| 1776 |
+
emocion_info = f"""
|
| 1777 |
+
### 🎭 Análisis Emocional
|
| 1778 |
+
|
| 1779 |
+
<div style="background: linear-gradient(135deg, {color}22 0%, {color}44 100%); padding: 15px; border-radius: 10px; border-left: 4px solid {color};">
|
| 1780 |
+
<p style="font-size: 18px; margin: 0;">
|
| 1781 |
+
<strong style="color: {color};">{emoji} Emoción detectada: {nombre}</strong>
|
| 1782 |
+
</p>
|
| 1783 |
+
<p style="margin: 5px 0 0 0; color: #666;">
|
| 1784 |
+
Nivel de confianza: {confianza:.1%}
|
| 1785 |
+
</p>
|
| 1786 |
+
</div>
|
| 1787 |
+
"""
|
| 1788 |
+
|
| 1789 |
+
yield (respuesta, audio, transcripcion, emocion_info, gr.update(visible=False))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1790 |
|
| 1791 |
btn_consulta_ia.click(
|
| 1792 |
fn=consultar_ia_con_loading,
|