Spaces:
Running
Running
Update core/integrations/doc_converter.py
Browse files- core/integrations/doc_converter.py +114 -98
core/integrations/doc_converter.py
CHANGED
@@ -1,98 +1,114 @@
|
|
1 |
-
# core/integrations/doc_converter
|
2 |
-
import os
|
3 |
-
import re
|
4 |
-
import uuid
|
5 |
-
import tempfile
|
6 |
-
import pypandoc
|
7 |
-
from loguru import logger
|
8 |
-
from fastapi.responses import FileResponse
|
9 |
-
|
10 |
-
# Control de descargas (máximo 2 por archivo)
|
11 |
-
_descargas = {}
|
12 |
-
|
13 |
-
|
14 |
-
def limpiar_lineas_hr(markdown_text: str) -> str:
|
15 |
-
"""Reemplaza líneas horizontales '---' por saltos de línea."""
|
16 |
-
return re.sub(r"^\s*---\s*$", "\n", markdown_text, flags=re.MULTILINE)
|
17 |
-
|
18 |
-
|
19 |
-
def normalizar_ecuaciones(md: str) -> str:
|
20 |
-
"""Convierte ecuaciones LaTeX escapadas a formato estándar."""
|
21 |
-
md = re.sub(r"\\\[\s*(.*?)\s*\\\]", r"$$\1$$", md, flags=re.DOTALL)
|
22 |
-
md = re.sub(r"\\\(\s*(.*?)\s*\\\)", r"$\1$", md, flags=re.DOTALL)
|
23 |
-
return md
|
24 |
-
|
25 |
-
|
26 |
-
def limpiar_backticks(markdown_text: str) -> str:
|
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 |
-
logger.
|
79 |
-
return {"
|
80 |
-
|
81 |
-
|
82 |
-
logger.
|
83 |
-
return {"error": "
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# core/integrations/doc_converter
|
2 |
+
import os
|
3 |
+
import re
|
4 |
+
import uuid
|
5 |
+
import tempfile
|
6 |
+
import pypandoc
|
7 |
+
from loguru import logger
|
8 |
+
from fastapi.responses import FileResponse
|
9 |
+
|
10 |
+
# Control de descargas (máximo 2 por archivo)
|
11 |
+
_descargas = {}
|
12 |
+
|
13 |
+
|
14 |
+
def limpiar_lineas_hr(markdown_text: str) -> str:
|
15 |
+
"""Reemplaza líneas horizontales '---' por saltos de línea."""
|
16 |
+
return re.sub(r"^\s*---\s*$", "\n", markdown_text, flags=re.MULTILINE)
|
17 |
+
|
18 |
+
|
19 |
+
def normalizar_ecuaciones(md: str) -> str:
|
20 |
+
"""Convierte ecuaciones LaTeX escapadas a formato estándar."""
|
21 |
+
md = re.sub(r"\\\[\s*(.*?)\s*\\\]", r"$$\1$$", md, flags=re.DOTALL)
|
22 |
+
md = re.sub(r"\\\(\s*(.*?)\s*\\\)", r"$\1$", md, flags=re.DOTALL)
|
23 |
+
return md
|
24 |
+
|
25 |
+
|
26 |
+
# def limpiar_backticks(markdown_text: str) -> str:
|
27 |
+
# """
|
28 |
+
# Elimina los backticks triples si encapsulan todo el contenido.
|
29 |
+
# """
|
30 |
+
# markdown_text = markdown_text.strip()
|
31 |
+
# if markdown_text.startswith("```") and markdown_text.endswith("```"):
|
32 |
+
# logger.info("🧹 Eliminando backticks triples de la respuesta LLM.")
|
33 |
+
# return markdown_text[3:-3].strip()
|
34 |
+
# return markdown_text
|
35 |
+
|
36 |
+
def limpiar_backticks(markdown_text: str) -> str:
|
37 |
+
"""
|
38 |
+
Elimina los backticks triples (``` o ```markdown) si encapsulan todo el contenido.
|
39 |
+
"""
|
40 |
+
markdown_text = markdown_text.strip()
|
41 |
+
|
42 |
+
# Elimina bloque inicial ``` o ```markdown y final ```
|
43 |
+
if markdown_text.startswith("```") and markdown_text.endswith("```"):
|
44 |
+
# Reemplaza ` ```markdown\n` por vacío
|
45 |
+
markdown_text = re.sub(r"^```[a-zA-Z]*\n?", "", markdown_text)
|
46 |
+
# Quita los ``` finales
|
47 |
+
markdown_text = re.sub(r"\n?```$", "", markdown_text)
|
48 |
+
logger.info("🧹 Eliminando bloque ``` del inicio y final.")
|
49 |
+
return markdown_text.strip()
|
50 |
+
|
51 |
+
return markdown_text
|
52 |
+
|
53 |
+
def procesar_markdown(markdown_content: str) -> dict:
|
54 |
+
try:
|
55 |
+
# Limpieza previa del contenido
|
56 |
+
markdown_content = limpiar_backticks(markdown_content)
|
57 |
+
contenido_limpio = normalizar_ecuaciones(limpiar_lineas_hr(markdown_content))
|
58 |
+
|
59 |
+
uid = str(uuid.uuid4())
|
60 |
+
temp_dir = tempfile.gettempdir()
|
61 |
+
input_md = os.path.join(temp_dir, f"{uid}.md")
|
62 |
+
output_docx = os.path.join(temp_dir, f"{uid}.docx")
|
63 |
+
|
64 |
+
with open(input_md, "w", encoding="utf-8") as f:
|
65 |
+
f.write(contenido_limpio)
|
66 |
+
|
67 |
+
pypandoc.convert_file(
|
68 |
+
source_file=input_md,
|
69 |
+
to="docx",
|
70 |
+
outputfile=output_docx,
|
71 |
+
format="md",
|
72 |
+
extra_args=["--standalone"],
|
73 |
+
)
|
74 |
+
|
75 |
+
os.remove(input_md)
|
76 |
+
_descargas[uid] = 0
|
77 |
+
|
78 |
+
logger.success(f"✅ DOCX generado correctamente: {output_docx}")
|
79 |
+
return {"message": "Archivo DOCX generado exitosamente.", "file_id": uid}
|
80 |
+
|
81 |
+
except Exception as e:
|
82 |
+
logger.error(f"❌ Error al procesar Markdown: {e}")
|
83 |
+
return {"error": "Fallo en la conversión de Markdown a DOCX."}
|
84 |
+
|
85 |
+
|
86 |
+
def gestionar_descarga(file_id: str):
|
87 |
+
"""
|
88 |
+
Controla la descarga de archivos. Permite solo 2 descargas por archivo.
|
89 |
+
"""
|
90 |
+
temp_dir = tempfile.gettempdir()
|
91 |
+
output_docx = os.path.join(temp_dir, f"{file_id}.docx")
|
92 |
+
|
93 |
+
if not os.path.exists(output_docx):
|
94 |
+
logger.warning(f"⚠️ Archivo no encontrado: {output_docx}")
|
95 |
+
return {"error": "El archivo no existe o fue eliminado.", "status": 404}
|
96 |
+
|
97 |
+
if file_id not in _descargas:
|
98 |
+
logger.warning(f"⚠️ ID inválido de descarga: {file_id}")
|
99 |
+
return {"error": "ID de archivo no válido.", "status": 400}
|
100 |
+
|
101 |
+
if _descargas[file_id] >= 2:
|
102 |
+
os.remove(output_docx)
|
103 |
+
del _descargas[file_id]
|
104 |
+
logger.info(f"🗑️ Archivo eliminado tras exceder descargas: {file_id}")
|
105 |
+
return {"error": "Límite de descargas alcanzado.", "status": 410}
|
106 |
+
|
107 |
+
_descargas[file_id] += 1
|
108 |
+
logger.info(f"⬇️ Descarga {_descargas[file_id]} de 2 para archivo: {file_id}")
|
109 |
+
|
110 |
+
return FileResponse(
|
111 |
+
path=output_docx,
|
112 |
+
filename="material_educativo.docx",
|
113 |
+
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
114 |
+
)
|