Spaces:
Runtime error
Runtime error
| from backend.providers.base import fetch_json | |
| import fitz # PyMuPDF | |
| import httpx | |
| import os | |
| import tempfile | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| async def resolve_pdf(identifier: str) -> dict: | |
| """Resolve PDF URL from DOI or identifier.""" | |
| steps = [] | |
| # Extract DOI | |
| doi = None | |
| if identifier.startswith("10."): | |
| doi = identifier | |
| elif "doi.org" in identifier or "10." in identifier: | |
| import re | |
| match = re.search(r'(10\.\d{4,}/[^\s]+)', identifier) | |
| if match: | |
| doi = match.group(1) | |
| if doi: | |
| steps.append(f"DOI detectado: {doi}") | |
| # Try Unpaywall | |
| data = await fetch_json(f"https://api.unpaywall.org/v2/{doi}?email=test@example.com") | |
| if "error" not in data and data.get("best_oa_location"): | |
| url = data["best_oa_location"].get("url_for_pdf") or data["best_oa_location"].get("url") | |
| if url: | |
| steps.append("Unpaywall resolvi贸") | |
| return {"pdfUrl": url, "resolvedFrom": "Unpaywall", "doi": doi, "steps": steps} | |
| # Try Semantic Scholar | |
| data = await fetch_json(f"https://api.semanticscholar.org/graph/v1/paper/DOI:{doi}?fields=openAccessPdf") | |
| if "error" not in data and data.get("openAccessPdf"): | |
| steps.append("Semantic Scholar resolvi贸") | |
| return {"pdfUrl": data["openAccessPdf"]["url"], "resolvedFrom": "Semantic Scholar", "doi": doi, "steps": steps} | |
| # Try DOI.org landing page | |
| steps.append("DOI.org como fallback") | |
| return {"pdfUrl": f"https://doi.org/{doi}", "resolvedFrom": "DOI.org", "doi": doi, "steps": steps} | |
| return {"error": "No se pudo resolver el identificador", "steps": steps} | |
| async def download_pdf(url: str) -> dict: | |
| """Descarga un PDF desde una URL y lo guarda en un archivo temporal.""" | |
| try: | |
| async with httpx.AsyncClient(follow_redirects=True, verify=False) as client: | |
| response = await client.get(url, timeout=30.0) | |
| response.raise_for_status() | |
| # Verificar si realmente es un PDF | |
| content_type = response.headers.get("Content-Type", "") | |
| if "pdf" not in content_type.lower() and not url.lower().endswith(".pdf"): | |
| # Algunos repositorios devuelven HTML (landing page) en lugar del PDF directo. | |
| # Como heur铆stica simple, si el contenido empieza con %PDF, lo procesamos. | |
| if not response.content.startswith(b"%PDF"): | |
| return {"error": f"La URL no retorn贸 un PDF v谩lido (Content-Type: {content_type})"} | |
| tmp_fd, tmp_path = tempfile.mkstemp(suffix=".pdf") | |
| with os.fdopen(tmp_fd, "wb") as f: | |
| f.write(response.content) | |
| return {"success": True, "path": tmp_path, "size": len(response.content)} | |
| except Exception as e: | |
| return {"error": f"Error descargando PDF: {str(e)}"} | |
| async def read_pdf(file_path: str) -> dict: | |
| """Extrae texto de un archivo PDF usando PyMuPDF.""" | |
| try: | |
| # Abrir el documento | |
| doc = fitz.open(file_path) | |
| text_pages = [] | |
| full_text = "" | |
| for i, page in enumerate(doc): | |
| page_text = page.get_text() | |
| text_pages.append(page_text) | |
| full_text += f"\n--- P谩gina {i+1} ---\n{page_text}" | |
| doc.close() | |
| # Eliminar el archivo temporal si es necesario | |
| if file_path.startswith(tempfile.gettempdir()): | |
| try: | |
| os.remove(file_path) | |
| except Exception: | |
| pass | |
| return { | |
| "success": True, | |
| "text": full_text, | |
| "pages": len(text_pages), | |
| "preview": full_text[:1000] | |
| } | |
| except Exception as e: | |
| return {"error": f"Error leyendo PDF: {str(e)}"} | |
| def chunk_text(text: str, chunk_size: int = 1500, chunk_overlap: int = 200) -> list: | |
| """Divide texto en fragmentos (chunks) usando LangChain.""" | |
| try: | |
| splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=chunk_size, | |
| chunk_overlap=chunk_overlap, | |
| separators=["\\n\\n", "\\n", ". ", " ", ""] | |
| ) | |
| chunks = splitter.split_text(text) | |
| return chunks | |
| except Exception as e: | |
| print(f"Error chunking text: {e}") | |
| return [text] | |