Spaces:
Paused
Paused
Upload 3 files
Browse files- app.py +38 -10
- gradio_theme_fenix.py +19 -0
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -5,13 +5,25 @@ Leer hebreo + español · escuchar (voces gratis) · estudiar con IA (Groq).
|
|
| 5 |
Los libros se cargan desde la carpeta libros/ (un JSON por libro).
|
| 6 |
Secret opcional para la pestaña Estudiar: GROQ_API_KEY
|
| 7 |
"""
|
| 8 |
-
import os, glob, json, tempfile, html
|
| 9 |
import urllib.request
|
| 10 |
import gradio as gr
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
from gradio_theme_fenix import fenix_theme, FENIX_CSS
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
MODELO_ESTUDIA = "openai/gpt-oss-120b"
|
| 16 |
CARPETA = "libros"
|
| 17 |
GROQ_URL = "https://api.groq.com/openai/v1/chat/completions"
|
|
@@ -77,20 +89,36 @@ def render(nombre, cap):
|
|
| 77 |
return f"<div class='pasaje'>{''.join(filas)}</div>", " ".join(plano_es), " ".join(plano_he)
|
| 78 |
|
| 79 |
|
| 80 |
-
# ---------- voces ----------
|
| 81 |
-
def a_voz(texto, idioma):
|
| 82 |
if not texto or not texto.strip():
|
| 83 |
return None
|
|
|
|
| 84 |
ruta = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
try:
|
| 87 |
-
gTTS(text=texto[:4500], lang=
|
| 88 |
return ruta
|
| 89 |
except Exception:
|
| 90 |
-
|
| 91 |
return None
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
# ---------- estudio (Groq por HTTP) ----------
|
| 95 |
def estudiar(mensaje, historial, contexto):
|
| 96 |
if not GROQ_KEY:
|
|
@@ -145,8 +173,8 @@ with gr.Blocks(theme=fenix_theme(), css=FENIX_CSS, title="Centralita Torá") as
|
|
| 145 |
return md, es, he, nombre, cap
|
| 146 |
|
| 147 |
ver_btn.click(ver, [dd, ncap], [salida, st_es, st_he, st_nombre, st_cap])
|
| 148 |
-
b_es.click(
|
| 149 |
-
b_he.click(
|
| 150 |
|
| 151 |
with gr.Tab("Buscar"):
|
| 152 |
q = gr.Textbox(label="Palabra o frase (hebreo o español)", placeholder="ej. luz / אור")
|
|
@@ -207,4 +235,4 @@ with gr.Blocks(theme=fenix_theme(), css=FENIX_CSS, title="Centralita Torá") as
|
|
| 207 |
)
|
| 208 |
|
| 209 |
if __name__ == "__main__":
|
| 210 |
-
demo.launch()
|
|
|
|
| 5 |
Los libros se cargan desde la carpeta libros/ (un JSON por libro).
|
| 6 |
Secret opcional para la pestaña Estudiar: GROQ_API_KEY
|
| 7 |
"""
|
| 8 |
+
import os, glob, json, tempfile, html, asyncio
|
| 9 |
import urllib.request
|
| 10 |
import gradio as gr
|
| 11 |
+
import edge_tts
|
| 12 |
+
try:
|
| 13 |
+
from gtts import gTTS
|
| 14 |
+
except Exception:
|
| 15 |
+
gTTS = None
|
| 16 |
|
| 17 |
from gradio_theme_fenix import fenix_theme, FENIX_CSS
|
| 18 |
|
| 19 |
+
# Voz única y alegre de la app (edge-tts, gratis). Cámbiala aquí si quieres otra:
|
| 20 |
+
# es: es-ES-ElviraNeural · es-ES-XimenaNeural · es-MX-DaliaNeural · es-CO-SalomeNeural
|
| 21 |
+
# he: he-IL-HilaNeural · he-IL-AvriNeural
|
| 22 |
+
VOZ_ES = "es-ES-ElviraNeural"
|
| 23 |
+
VOZ_HE = "he-IL-HilaNeural"
|
| 24 |
+
VOZ_RATE = "+8%" # un poco más ágil = más alegre
|
| 25 |
+
VOZ_PITCH = "+10Hz" # tono algo más alto = más alegre
|
| 26 |
+
|
| 27 |
MODELO_ESTUDIA = "openai/gpt-oss-120b"
|
| 28 |
CARPETA = "libros"
|
| 29 |
GROQ_URL = "https://api.groq.com/openai/v1/chat/completions"
|
|
|
|
| 89 |
return f"<div class='pasaje'>{''.join(filas)}</div>", " ".join(plano_es), " ".join(plano_he)
|
| 90 |
|
| 91 |
|
| 92 |
+
# ---------- voces (edge-tts: voz única y alegre, gratis) ----------
|
| 93 |
+
async def a_voz(texto, idioma):
|
| 94 |
if not texto or not texto.strip():
|
| 95 |
return None
|
| 96 |
+
voz = VOZ_HE if idioma == "he" else VOZ_ES
|
| 97 |
ruta = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name
|
| 98 |
+
try:
|
| 99 |
+
com = edge_tts.Communicate(texto[:4000], voz, rate=VOZ_RATE, pitch=VOZ_PITCH)
|
| 100 |
+
await com.save(ruta)
|
| 101 |
+
if os.path.getsize(ruta) > 0:
|
| 102 |
+
return ruta
|
| 103 |
+
except Exception:
|
| 104 |
+
pass
|
| 105 |
+
if gTTS: # respaldo por si edge-tts falla
|
| 106 |
try:
|
| 107 |
+
gTTS(text=texto[:4500], lang=("iw" if idioma == "he" else "es")).save(ruta)
|
| 108 |
return ruta
|
| 109 |
except Exception:
|
| 110 |
+
return None
|
| 111 |
return None
|
| 112 |
|
| 113 |
|
| 114 |
+
async def leer_es(t):
|
| 115 |
+
return await a_voz(t, "es")
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
async def leer_he(t):
|
| 119 |
+
return await a_voz(t, "he")
|
| 120 |
+
|
| 121 |
+
|
| 122 |
# ---------- estudio (Groq por HTTP) ----------
|
| 123 |
def estudiar(mensaje, historial, contexto):
|
| 124 |
if not GROQ_KEY:
|
|
|
|
| 173 |
return md, es, he, nombre, cap
|
| 174 |
|
| 175 |
ver_btn.click(ver, [dd, ncap], [salida, st_es, st_he, st_nombre, st_cap])
|
| 176 |
+
b_es.click(leer_es, st_es, audio)
|
| 177 |
+
b_he.click(leer_he, st_he, audio)
|
| 178 |
|
| 179 |
with gr.Tab("Buscar"):
|
| 180 |
q = gr.Textbox(label="Palabra o frase (hebreo o español)", placeholder="ej. luz / אור")
|
|
|
|
| 235 |
)
|
| 236 |
|
| 237 |
if __name__ == "__main__":
|
| 238 |
+
demo.launch(ssr_mode=False)
|
gradio_theme_fenix.py
CHANGED
|
@@ -61,4 +61,23 @@ FENIX_CSS = """
|
|
| 61 |
.resultado { padding:6px 2px; border-bottom:1px dashed rgba(120,150,200,.25); }
|
| 62 |
.resultado .ref { color:#c69a34; font-weight:700; }
|
| 63 |
footer { visibility: hidden; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
"""
|
|
|
|
| 61 |
.resultado { padding:6px 2px; border-bottom:1px dashed rgba(120,150,200,.25); }
|
| 62 |
.resultado .ref { color:#c69a34; font-weight:700; }
|
| 63 |
footer { visibility: hidden; }
|
| 64 |
+
|
| 65 |
+
/* ---------- responsive (móvil) ---------- */
|
| 66 |
+
@media (max-width: 680px) {
|
| 67 |
+
.gradio-container { max-width: 100% !important; padding: 0 8px !important; }
|
| 68 |
+
#cabecera { padding: 6px 0 2px; }
|
| 69 |
+
#cabecera .estrella { font-size: 26px; }
|
| 70 |
+
#cabecera h1 { font-size: 1.6rem; }
|
| 71 |
+
#cabecera p { font-size: .95rem; }
|
| 72 |
+
.pasaje .he { font-size: 1.45rem; line-height:1.7; }
|
| 73 |
+
.pasaje .es { font-size: 1rem; }
|
| 74 |
+
.pasaje .cab .h { font-size: 1.5rem; }
|
| 75 |
+
/* botones a lo ancho y fáciles de tocar */
|
| 76 |
+
.gr-button { min-height: 46px !important; font-size: 1rem !important; }
|
| 77 |
+
.gr-button, .gr-box, .gr-form { width: 100% !important; }
|
| 78 |
+
}
|
| 79 |
+
@media (max-width: 420px) {
|
| 80 |
+
#cabecera h1 { font-size: 1.4rem; }
|
| 81 |
+
.pasaje .he { font-size: 1.3rem; }
|
| 82 |
+
}
|
| 83 |
"""
|
requirements.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
|
|
|
| 1 |
gtts
|
|
|
|
| 1 |
+
edge-tts
|
| 2 |
gtts
|