Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ Leer hebreo real + español fluido con exégesis de Groq y audio optimizado
|
|
| 5 |
"""
|
| 6 |
import os, glob, json, tempfile, html, asyncio, subprocess, shutil, re
|
| 7 |
import urllib.request
|
|
|
|
| 8 |
import gradio as gr
|
| 9 |
import edge_tts
|
| 10 |
try:
|
|
@@ -57,8 +58,10 @@ def traducir_capitulo_con_groq(pares):
|
|
| 57 |
Si Groq no está disponible o falla, cae al literal español (o al hebreo).
|
| 58 |
"""
|
| 59 |
fallback = {n: (es or he) for (n, he, es) in pares}
|
| 60 |
-
if not
|
| 61 |
-
return fallback
|
|
|
|
|
|
|
| 62 |
|
| 63 |
# Bloque de entrada numerado: el hebreo es la fuente; el literal español,
|
| 64 |
# si existe, va solo como ayuda.
|
|
@@ -96,9 +99,22 @@ def traducir_capitulo_con_groq(pares):
|
|
| 96 |
with urllib.request.urlopen(req, timeout=120) as r:
|
| 97 |
d = json.load(r)
|
| 98 |
salida = (d["choices"][0]["message"]["content"] or "").strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
except Exception as e:
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
# Parseamos las líneas '[N] texto' a un diccionario por versículo.
|
| 104 |
res = {}
|
|
@@ -107,8 +123,14 @@ def traducir_capitulo_con_groq(pares):
|
|
| 107 |
if m:
|
| 108 |
res[int(m.group(1))] = m.group(2).strip()
|
| 109 |
|
|
|
|
|
|
|
|
|
|
| 110 |
# Si Groq se saltó algún versículo, se completa con el fallback.
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
# --- PROCESAMIENTO ACÚSTICO FFMPEG ---
|
|
@@ -390,10 +412,17 @@ def render(nombre, cap):
|
|
| 390 |
|
| 391 |
# Groq da SENTIDO a las palabras ANTES de mostrarlas: traduce el capítulo
|
| 392 |
# desde el hebreo, versículo por versículo.
|
| 393 |
-
es_por_verso = traducir_capitulo_con_groq(pares)
|
| 394 |
|
| 395 |
# Interfaz: hebreo intacto arriba y, debajo, la traducción con sentido.
|
| 396 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
f"{html.escape(d.get('es',''))} {cap}</div>"]
|
| 398 |
|
| 399 |
partes_es = []
|
|
@@ -496,6 +525,31 @@ with gr.Blocks(theme=fenix_theme(), css=FENIX_CSS, title="Centralita Torá") as
|
|
| 496 |
|
| 497 |
q_btn.click(buscar, q, q_out)
|
| 498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
with gr.Tab("Estudiar"):
|
| 500 |
gr.Markdown("La IA usa el capítulo abierto en **Leer** como contexto.")
|
| 501 |
chat = gr.Chatbot(type="messages", height=360)
|
|
@@ -528,4 +582,4 @@ with gr.Blocks(theme=fenix_theme(), css=FENIX_CSS, title="Centralita Torá") as
|
|
| 528 |
)
|
| 529 |
|
| 530 |
if __name__ == "__main__":
|
| 531 |
-
demo.launch(ssr_mode=False)
|
|
|
|
| 5 |
"""
|
| 6 |
import os, glob, json, tempfile, html, asyncio, subprocess, shutil, re
|
| 7 |
import urllib.request
|
| 8 |
+
import urllib.error
|
| 9 |
import gradio as gr
|
| 10 |
import edge_tts
|
| 11 |
try:
|
|
|
|
| 58 |
Si Groq no está disponible o falla, cae al literal español (o al hebreo).
|
| 59 |
"""
|
| 60 |
fallback = {n: (es or he) for (n, he, es) in pares}
|
| 61 |
+
if not pares:
|
| 62 |
+
return fallback, "sin_pares"
|
| 63 |
+
if not GROQ_KEY:
|
| 64 |
+
return fallback, "sin_key"
|
| 65 |
|
| 66 |
# Bloque de entrada numerado: el hebreo es la fuente; el literal español,
|
| 67 |
# si existe, va solo como ayuda.
|
|
|
|
| 99 |
with urllib.request.urlopen(req, timeout=120) as r:
|
| 100 |
d = json.load(r)
|
| 101 |
salida = (d["choices"][0]["message"]["content"] or "").strip()
|
| 102 |
+
except urllib.error.HTTPError as e:
|
| 103 |
+
cuerpo_error = ""
|
| 104 |
+
try:
|
| 105 |
+
cuerpo_error = e.read().decode("utf-8", errors="replace")[:300]
|
| 106 |
+
except Exception:
|
| 107 |
+
pass
|
| 108 |
+
motivo = f"error HTTP {e.code}: {cuerpo_error or e.reason}"
|
| 109 |
+
print(f"Error traduciendo con Groq: {motivo}")
|
| 110 |
+
return fallback, motivo
|
| 111 |
except Exception as e:
|
| 112 |
+
motivo = f"error: {type(e).__name__}: {e}"
|
| 113 |
+
print(f"Error traduciendo con Groq: {motivo}")
|
| 114 |
+
return fallback, motivo
|
| 115 |
+
|
| 116 |
+
if not salida:
|
| 117 |
+
return fallback, "respuesta vacía de Groq"
|
| 118 |
|
| 119 |
# Parseamos las líneas '[N] texto' a un diccionario por versículo.
|
| 120 |
res = {}
|
|
|
|
| 123 |
if m:
|
| 124 |
res[int(m.group(1))] = m.group(2).strip()
|
| 125 |
|
| 126 |
+
if not res:
|
| 127 |
+
return fallback, "no se pudo interpretar la respuesta de Groq"
|
| 128 |
+
|
| 129 |
# Si Groq se saltó algún versículo, se completa con el fallback.
|
| 130 |
+
final = {n: res.get(n, fallback[n]) for (n, he, es) in pares}
|
| 131 |
+
faltantes = sum(1 for (n, he, es) in pares if n not in res)
|
| 132 |
+
estado = "ok" if faltantes == 0 else f"ok (Groq omitió {faltantes} versículo(s), se completó con literal)"
|
| 133 |
+
return final, estado
|
| 134 |
|
| 135 |
|
| 136 |
# --- PROCESAMIENTO ACÚSTICO FFMPEG ---
|
|
|
|
| 412 |
|
| 413 |
# Groq da SENTIDO a las palabras ANTES de mostrarlas: traduce el capítulo
|
| 414 |
# desde el hebreo, versículo por versículo.
|
| 415 |
+
es_por_verso, estado_groq = traducir_capitulo_con_groq(pares)
|
| 416 |
|
| 417 |
# Interfaz: hebreo intacto arriba y, debajo, la traducción con sentido.
|
| 418 |
+
if estado_groq == "ok" or estado_groq.startswith("ok ("):
|
| 419 |
+
aviso = f"<div class='aviso-groq' style='color:#2a7a2a'>✅ Traducción con sentido (Groq) — {estado_groq}</div>" if estado_groq != "ok" else ""
|
| 420 |
+
elif estado_groq == "sin_key":
|
| 421 |
+
aviso = "<div class='aviso-groq' style='color:#b00'>⚠️ Mostrando literal: falta el Secret GROQ_API_KEY.</div>"
|
| 422 |
+
else:
|
| 423 |
+
aviso = f"<div class='aviso-groq' style='color:#b00'>⚠️ Mostrando literal: falló Groq ({html.escape(estado_groq)}).</div>"
|
| 424 |
+
|
| 425 |
+
filas = [aviso, f"<div class='cab'><span class='h'>{html.escape(_limpiar_entidades(d.get('heb','')))}</span><br>"
|
| 426 |
f"{html.escape(d.get('es',''))} {cap}</div>"]
|
| 427 |
|
| 428 |
partes_es = []
|
|
|
|
| 525 |
|
| 526 |
q_btn.click(buscar, q, q_out)
|
| 527 |
|
| 528 |
+
with gr.Tab("Buscar"):
|
| 529 |
+
q = gr.Textbox(label="Palabra o frase (hebreo o español)", placeholder="ej. luz / אור")
|
| 530 |
+
q_btn = gr.Button("Buscar", variant="primary")
|
| 531 |
+
q_out = gr.HTML()
|
| 532 |
+
|
| 533 |
+
def buscar(texto):
|
| 534 |
+
t = (texto or "").strip().lower()
|
| 535 |
+
if not t:
|
| 536 |
+
return "<p>Escribe algo para buscar.</p>"
|
| 537 |
+
filas = []
|
| 538 |
+
for lid in IDS:
|
| 539 |
+
d = LIBROS[lid]
|
| 540 |
+
for c, vs in d.get("capitulos", {}).items():
|
| 541 |
+
for v in vs:
|
| 542 |
+
campos = " ".join(filter(None, [v.get("es"), v.get("he")])).lower()
|
| 543 |
+
if t in campos:
|
| 544 |
+
ref = f"{d.get('es', lid)} {c}:{v.get('n')}"
|
| 545 |
+
txt = html.escape(_limpiar_entidades(v.get("es") or v.get("he") or ""))
|
| 546 |
+
filas.append(f"<div class='resultado'><span class='ref'>{ref}</span> — {txt}</div>")
|
| 547 |
+
if len(filas) >= 120:
|
| 548 |
+
return "".join(filas)
|
| 549 |
+
return "".join(filas) if filas else "<p>Sin resultados en los libros cargados.</p>"
|
| 550 |
+
|
| 551 |
+
q_btn.click(buscar, q, q_out)
|
| 552 |
+
|
| 553 |
with gr.Tab("Estudiar"):
|
| 554 |
gr.Markdown("La IA usa el capítulo abierto en **Leer** como contexto.")
|
| 555 |
chat = gr.Chatbot(type="messages", height=360)
|
|
|
|
| 582 |
)
|
| 583 |
|
| 584 |
if __name__ == "__main__":
|
| 585 |
+
demo.launch(ssr_mode=False)
|