diff --git a/routes/document_routes.py b/routes/document_routes.py index 9ae2994..bae8bdb 100644 --- a/routes/document_routes.py +++ b/routes/document_routes.py @@ -30,6 +30,15 @@ def _locate_current_user_upload(request: Request, upload_dir: str, upload_id: st return _locate_upload(upload_dir, upload_id, owner=user, auth_manager=auth_manager) +def _load_pdf_viewer_fitz(): + from src.pdf_runtime import load_pymupdf_for_pdf_viewer + + try: + return load_pymupdf_for_pdf_viewer() + except RuntimeError as exc: + raise HTTPException(503, str(exc)) from exc + + def setup_document_routes(session_manager, upload_handler=None) -> APIRouter: router = APIRouter(tags=["documents"]) @@ -972,7 +981,6 @@ def setup_document_routes(session_manager, upload_handler=None) -> APIRouter: """ from src.pdf_form_doc import find_source_upload_id, parse_markdown_to_values, load_field_sidecar from src.constants import UPLOAD_DIR - import fitz user = get_current_user(request) db = SessionLocal() @@ -988,6 +996,7 @@ def setup_document_routes(session_manager, upload_handler=None) -> APIRouter: if not pdf_path: raise HTTPException(404, f"Source PDF {upload_id} not found") + fitz = _load_pdf_viewer_fitz() schema = load_field_sidecar(pdf_path) or [] values = parse_markdown_to_values(doc.current_content or "") @@ -1040,7 +1049,6 @@ def setup_document_routes(session_manager, upload_handler=None) -> APIRouter: from fastapi.responses import Response from src.pdf_form_doc import find_source_upload_id from src.constants import UPLOAD_DIR - import fitz user = get_current_user(request) db = SessionLocal() @@ -1058,6 +1066,7 @@ def setup_document_routes(session_manager, upload_handler=None) -> APIRouter: finally: db.close() + fitz = _load_pdf_viewer_fitz() pdf_doc = fitz.open(pdf_path) try: if page_no < 1 or page_no > pdf_doc.page_count: diff --git a/src/pdf_runtime.py b/src/pdf_runtime.py new file mode 100644 index 0000000..40d5016 --- /dev/null +++ b/src/pdf_runtime.py @@ -0,0 +1,15 @@ +"""Small helpers for optional PDF runtime dependencies.""" + +PDF_VIEWER_PYMUPDF_MISSING = ( + "PDF viewer requires PyMuPDF. Install optional PDF dependencies with " + "`pip install -r requirements-optional.txt` (PyMuPDF is AGPL-3.0)." +) + + +def load_pymupdf_for_pdf_viewer(): + """Return the PyMuPDF module, or raise a user-facing setup hint.""" + try: + import fitz # PyMuPDF, optional + except ImportError as exc: + raise RuntimeError(PDF_VIEWER_PYMUPDF_MISSING) from exc + return fitz diff --git a/static/js/document.js b/static/js/document.js index fe8084a..2d8b8e4 100644 --- a/static/js/document.js +++ b/static/js/document.js @@ -1106,6 +1106,16 @@ import * as Modals from './modalManager.js'; }); } + async function _pdfResponseErrorMessage(res) { + const text = await res.text().catch(() => ''); + try { + const data = JSON.parse(text); + if (typeof data?.detail === 'string') return data.detail; + if (data?.detail) return JSON.stringify(data.detail); + } catch (_) {} + return text || res.statusText || `HTTP ${res.status}`; + } + async function _renderPdfPane() { const pane = document.getElementById('doc-pdf-view'); if (!pane || !activeDocId) return; @@ -1118,7 +1128,7 @@ import * as Modals from './modalManager.js'; let data; try { const res = await fetch(`${API_BASE}/api/document/${docId}/render-pages`); - if (!res.ok) throw new Error(await res.text()); + if (!res.ok) throw new Error(await _pdfResponseErrorMessage(res)); data = await res.json(); } catch (e) { pane.innerHTML = `