Spaces:
Running on Zero
Running on Zero
| from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks | |
| from fastapi.responses import JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import os | |
| import uuid | |
| from pathlib import Path | |
| from typing import Dict, List, Optional | |
| import logging | |
| from datetime import datetime | |
| import json | |
| from pdf_processor import PDFProcessor | |
| from mcp_server import update_document_data, remove_document | |
| from anthropic_client import AnthropicClient | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI(title="PDF Parser MCP Server", version="1.0.0") | |
| # Configure CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Initialize components | |
| pdf_processor = PDFProcessor() | |
| anthropic_client = AnthropicClient() | |
| # Storage for processed documents | |
| processed_documents: Dict[str, Dict] = {} | |
| # Ensure upload directory exists | |
| UPLOAD_DIR = Path(os.getenv("UPLOAD_DIR", "uploads")) | |
| UPLOAD_DIR.mkdir(exist_ok=True) | |
| STORAGE_FILE = Path("storage.json") | |
| def save_documents(): | |
| with open(STORAGE_FILE, "w", encoding="utf-8") as f: | |
| json.dump(processed_documents, f, ensure_ascii=False, indent=2) | |
| def load_documents(): | |
| global processed_documents | |
| if STORAGE_FILE.exists(): | |
| with open(STORAGE_FILE, "r", encoding="utf-8") as f: | |
| processed_documents.clear() | |
| processed_documents.update(json.load(f)) | |
| # Load documents on startup | |
| load_documents() | |
| async def root(): | |
| """Welcome message and API information""" | |
| return { | |
| "message": "PDF Parser MCP Server API", | |
| "version": "1.0.0", | |
| "docs": "/docs", | |
| "health": "/health", | |
| "endpoints": { | |
| "upload": "POST /upload-pdf/", | |
| "list_documents": "GET /documents/", | |
| "document_info": "GET /documents/{file_id}", | |
| "document_content": "GET /documents/{file_id}/content", | |
| "document_summary": "GET /documents/{file_id}/summary", | |
| "document_chunks": "GET /documents/{file_id}/chunks", | |
| "full_document": "GET /documents/{file_id}/full" | |
| } | |
| } | |
| async def upload_pdf( | |
| background_tasks: BackgroundTasks, | |
| file: UploadFile = File(...) | |
| ): | |
| """Upload and process a PDF file""" | |
| # Validate file type | |
| if not file.filename.endswith('.pdf'): | |
| raise HTTPException(status_code=400, detail="Only PDF files are allowed") | |
| # Generate unique file ID | |
| file_id = str(uuid.uuid4()) | |
| file_path = UPLOAD_DIR / f"{file_id}.pdf" | |
| try: | |
| # Save uploaded file | |
| with open(file_path, "wb") as buffer: | |
| content = await file.read() | |
| buffer.write(content) | |
| # Process PDF in background | |
| background_tasks.add_task(process_pdf_background, file_id, file_path, file.filename) | |
| return JSONResponse({ | |
| "file_id": file_id, | |
| "filename": file.filename, | |
| "status": "processing", | |
| "message": "PDF uploaded successfully. Processing in background." | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error uploading file: {str(e)}") | |
| raise HTTPException(status_code=500, detail=f"Error uploading file: {str(e)}") | |
| async def process_pdf_background(file_id: str, file_path: Path, filename: str): | |
| """Background task to process PDF""" | |
| try: | |
| logger.info(f"Processing PDF: {filename}") | |
| # Extract text from PDF | |
| extracted_text = pdf_processor.extract_text(file_path) | |
| # Chunk text if needed | |
| chunks = pdf_processor.chunk_text(extracted_text) | |
| # Generate summary using Anthropic | |
| summary = await anthropic_client.generate_summary(chunks) | |
| # Store processed document | |
| processed_documents[file_id] = { | |
| "filename": filename, | |
| "extracted_text": extracted_text, | |
| "chunks": chunks, | |
| "summary": summary, | |
| "processed_at": datetime.now().isoformat(), | |
| "status": "completed" | |
| } | |
| # Update MCP server data | |
| update_document_data(file_id, processed_documents[file_id]) | |
| logger.info(f"Successfully processed PDF: {filename}") | |
| except Exception as e: | |
| logger.error(f"Error processing PDF {filename}: {str(e)}") | |
| processed_documents[file_id] = { | |
| "filename": filename, | |
| "status": "error", | |
| "error": str(e), | |
| "processed_at": datetime.now().isoformat() | |
| } | |
| finally: | |
| save_documents() | |
| async def get_processing_status(file_id: str): | |
| """Get processing status of a PDF""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="File not found") | |
| doc = processed_documents[file_id] | |
| return { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "status": doc.get("status"), | |
| "processed_at": doc.get("processed_at"), | |
| "has_summary": "summary" in doc, | |
| "error": doc.get("error") | |
| } | |
| async def list_documents(): | |
| """List all processed documents""" | |
| return { | |
| "documents": [ | |
| { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "status": doc.get("status"), | |
| "processed_at": doc.get("processed_at"), | |
| "has_text": "extracted_text" in doc, | |
| "has_summary": "summary" in doc, | |
| "has_chunks": "chunks" in doc | |
| } | |
| for file_id, doc in processed_documents.items() | |
| ] | |
| } | |
| async def get_document_info(file_id: str): | |
| """Get document metadata and info""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| doc = processed_documents[file_id] | |
| return { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "status": doc.get("status"), | |
| "processed_at": doc.get("processed_at"), | |
| "has_text": "extracted_text" in doc, | |
| "has_summary": "summary" in doc, | |
| "has_chunks": "chunks" in doc, | |
| "text_length": len(doc.get("extracted_text", "")), | |
| "num_chunks": len(doc.get("chunks", [])), | |
| "error": doc.get("error") | |
| } | |
| async def get_document_content(file_id: str): | |
| """Get extracted text content of a document""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| doc = processed_documents[file_id] | |
| if "extracted_text" not in doc: | |
| raise HTTPException(status_code=404, detail="Extracted text not available") | |
| return { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "extracted_text": doc["extracted_text"], | |
| "text_length": len(doc["extracted_text"]) | |
| } | |
| async def get_document_summary(file_id: str): | |
| """Get AI-generated summary of a document""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| doc = processed_documents[file_id] | |
| if "summary" not in doc: | |
| raise HTTPException(status_code=404, detail="Summary not available") | |
| return { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "summary": doc["summary"] | |
| } | |
| async def get_document_chunks(file_id: str): | |
| """Get text chunks of a document""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| doc = processed_documents[file_id] | |
| if "chunks" not in doc: | |
| raise HTTPException(status_code=404, detail="Chunks not available") | |
| return { | |
| "file_id": file_id, | |
| "filename": doc.get("filename"), | |
| "chunks": doc["chunks"], | |
| "num_chunks": len(doc["chunks"]) | |
| } | |
| async def get_full_document(file_id: str): | |
| """Get complete document data (text, summary, chunks)""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| return processed_documents[file_id] | |
| async def delete_document(file_id: str): | |
| """Delete a processed document""" | |
| if file_id not in processed_documents: | |
| raise HTTPException(status_code=404, detail="Document not found") | |
| # Remove from storage | |
| del processed_documents[file_id] | |
| # Remove from MCP server | |
| remove_document(file_id) | |
| # Remove uploaded file | |
| file_path = UPLOAD_DIR / f"{file_id}.pdf" | |
| if file_path.exists(): | |
| file_path.unlink() | |
| save_documents() | |
| return {"message": "Document deleted successfully"} | |
| # Keep the old endpoints for backwards compatibility | |
| async def get_document(file_id: str): | |
| """Get full document data (deprecated - use /documents/{file_id}/full)""" | |
| return await get_full_document(file_id) | |
| async def delete_document_old(file_id: str): | |
| """Delete a processed document (deprecated - use /documents/{file_id})""" | |
| return await delete_document(file_id) | |
| async def health_check(): | |
| """Health check endpoint""" | |
| return {"status": "healthy", "timestamp": datetime.now().isoformat()} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) | |