CoolFace
Apppublic

Revathi2006/document_ai_pdf

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
backend.py512 linesDownload Raw Back to root
1import os2import uuid3import pdfplumber4import chromadb5from fastapi import FastAPI, UploadFile, HTTPException, Form, Request6from fastapi.responses import JSONResponse, HTMLResponse7from fastapi.middleware.cors import CORSMiddleware8from fastapi.staticfiles import StaticFiles9from fastapi.templating import Jinja2Templates10from sentence_transformers import SentenceTransformer11from typing import List, Optional12from dotenv import load_dotenv13import logging14from pathlib import Path15import io16import re17import requests18import json19import time20 21# Configure logging22logging.basicConfig(level=logging.INFO)23logger = logging.getLogger(__name__)24 25# Load environment variables26load_dotenv()27 28# Configuration29CHROMA_DB_PATH = "./chroma_db"30EMBEDDING_MODEL = "all-MiniLM-L6-v2"31HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY")32CHUNK_SIZE = 50033 34# Create necessary directories35Path("static").mkdir(exist_ok=True)36Path("templates").mkdir(exist_ok=True)37 38app = FastAPI(title="DOC AI", description="AI-powered document analysis tool")39 40# Serve static files and templates41app.mount("/static", StaticFiles(directory="static"), name="static")42templates = Jinja2Templates(directory="templates")43 44# CORS Setup45app.add_middleware(46    CORSMiddleware,47    allow_origins=["*"],48    allow_methods=["*"],49    allow_headers=["*"],50)51 52def chunk_text(text: str, chunk_size: int = CHUNK_SIZE) -> List[str]:53    """Split text into chunks while preserving sentence boundaries"""54    sentences = re.split(r'(?<=[.!?])\s+', text)55    56    chunks = []57    current_chunk = ""58    59    for sentence in sentences:60        if len(current_chunk) + len(sentence) < chunk_size:61            current_chunk += " " + sentence if current_chunk else sentence62        else:63            if current_chunk:64                chunks.append(current_chunk.strip())65            current_chunk = sentence66    67    if current_chunk.strip():68        chunks.append(current_chunk.strip())69    70    return chunks71 72# Initialize services73try:74    chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)75    document_collection = chroma_client.get_or_create_collection(76        name="documents",77        metadata={"hnsw:space": "cosine"}78    )79    embedder = SentenceTransformer(EMBEDDING_MODEL)80    logger.info("Services initialized successfully")81except Exception as e:82    logger.error(f"Service Init Error: {e}")83    raise84 85class DictionaryService:86    def __init__(self):87        self.session = requests.Session()88        self.session.headers.update({89            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'90        })91    92    def fetch_free_dictionary_api(self, word: str) -> List[str]:93        """Fetch definitions from Free Dictionary API (free, no key required)"""94        try:95            url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"96            logger.info(f"Fetching definition from Free Dictionary API for: {word}")97            98            response = self.session.get(url, timeout=10)99            100            if response.status_code == 200:101                data = response.json()102                definitions = []103                104                if isinstance(data, list) and len(data) > 0:105                    for meaning in data[0].get('meanings', []):106                        part_of_speech = meaning.get('partOfSpeech', '')107                        for definition in meaning.get('definitions', []):108                            def_text = definition.get('definition', '').strip()109                            if def_text:110                                if part_of_speech:111                                    definitions.append(f"({part_of_speech}) {def_text}")112                                else:113                                    definitions.append(def_text)114                115                logger.info(f"Found {len(definitions)} definitions for {word}")116                return definitions[:8]  # Return first 8 definitions117                118            elif response.status_code == 404:119                logger.warning(f"Word '{word}' not found in Free Dictionary API")120                return []121            else:122                logger.warning(f"Free Dictionary API returned status: {response.status_code}")123                return []124                125        except Exception as e:126            logger.warning(f"Free Dictionary API failed: {e}")127            return []128    129    def fetch_urbandictionary(self, word: str) -> List[str]:130        """Fetch definitions from Urban Dictionary (good for modern/slang terms)"""131        try:132            url = f"https://api.urbandictionary.com/v0/define?term={word}"133            logger.info(f"Fetching definition from Urban Dictionary for: {word}")134            135            response = self.session.get(url, timeout=10)136            137            if response.status_code == 200:138                data = response.json()139                definitions = []140                141                for item in data.get('list', [])[:3]:  # Get first 3 definitions142                    def_text = item.get('definition', '').strip()143                    # Clean up Urban Dictionary formatting144                    def_text = re.sub(r'\[.*?\]', '', def_text)  # Remove [word] links145                    if def_text and len(def_text) > 10:146                        definitions.append(def_text)147                148                logger.info(f"Found {len(definitions)} definitions from Urban Dictionary")149                return definitions150            else:151                return []152                153        except Exception as e:154            logger.warning(f"Urban Dictionary failed: {e}")155            return []156    157    def fetch_merriam_webster(self, word: str) -> List[str]:158        """Fetch definitions from Merriam-Webster (web scraping fallback)"""159        try:160            url = f"https://www.merriam-webster.com/dictionary/{word}"161            logger.info(f"Fetching definition from Merriam-Webster for: {word}")162            163            response = self.session.get(url, timeout=10)164            165            if response.status_code == 200:166                # Simple regex to extract definitions167                html = response.text168                definitions = []169                170                # Look for definition patterns171                patterns = [172                    r'<span class="dt-text">([^<]+)</span>',173                    r'<p class="definition-inner-item"[^>]*>([^<]+)</p>',174                    r'<div class="vg">([^<]+)</div>'175                ]176                177                for pattern in patterns:178                    matches = re.findall(pattern, html)179                    for match in matches:180                        def_text = match.strip()181                        if def_text and len(def_text) > 10 and word.lower() in def_text.lower():182                            definitions.append(def_text)183                184                logger.info(f"Found {len(definitions)} definitions from Merriam-Webster")185                return definitions[:5]186            else:187                return []188                189        except Exception as e:190            logger.warning(f"Merriam-Webster failed: {e}")191            return []192    193    def get_definitions(self, word: str) -> List[str]:194        """Get definitions from multiple reliable sources"""195        logger.info(f"Looking up definitions for: {word}")196        197        # Try multiple sources198        sources = [199            self.fetch_free_dictionary_api(word),  # Primary source200            self.fetch_urbandictionary(word),      # For modern terms201            self.fetch_merriam_webster(word)       # Fallback202        ]203        204        all_definitions = []205        seen_definitions = set()206        207        for definitions in sources:208            for definition in definitions:209                # Clean and deduplicate210                clean_def = definition.strip()211                if (len(clean_def) > 10 and 212                    clean_def not in seen_definitions and 213                    not clean_def.startswith('http')):214                    seen_definitions.add(clean_def)215                    all_definitions.append(clean_def)216        217        # Remove near-duplicates218        unique_definitions = []219        for definition in all_definitions:220            is_duplicate = False221            for existing in unique_definitions:222                similarity = len(set(definition.lower().split()) & set(existing.lower().split()))223                if similarity > 3:  # If more than 3 words overlap224                    is_duplicate = True225                    break226            if not is_duplicate:227                unique_definitions.append(definition)228        229        logger.info(f"Total unique definitions found: {len(unique_definitions)}")230        return unique_definitions[:10]  # Return up to 10 definitions231 232# Initialize dictionary service233dict_service = DictionaryService()234 235def create_local_summary(text: str, max_sentences: int = 8) -> str:236    """Create a simple summary by extracting key sentences"""237    sentences = re.split(r'(?<=[.!?])\s+', text)238    sentences = [s.strip() for s in sentences if len(s.strip()) > 20]239    240    if not sentences:241        return "Unable to generate summary from the provided text."242    243    # Score sentences (simple heuristic)244    scored_sentences = []245    for i, sentence in enumerate(sentences):246        score = len(sentence)247        if i < len(sentences) * 0.3:248            score *= 1.2249        key_terms = ['summary', 'conclusion', 'important', 'key', 'main', 'primary', 'result']250        if any(term in sentence.lower() for term in key_terms):251            score *= 1.5252        scored_sentences.append((sentence, score))253    254    scored_sentences.sort(key=lambda x: x[1], reverse=True)255    top_sentences = [s[0] for s in scored_sentences[:max_sentences]]256    257    final_sentences = []258    for sentence in sentences:259        if sentence in top_sentences:260            final_sentences.append(sentence)261    262    summary = " ".join(final_sentences)263    264    return summary265 266@app.get("/", response_class=HTMLResponse)267async def serve_frontend(request: Request):268    return templates.TemplateResponse("index.html", {"request": request})269 270@app.post("/upload")271async def upload_file(file: UploadFile):272    if not file.filename.lower().endswith('.pdf'):273        raise HTTPException(400, "Only PDF files are accepted.")274    275    try:276        file_content = await file.read()277        278        with pdfplumber.open(io.BytesIO(file_content)) as pdf:279            text = "\n".join(page.extract_text() or "" for page in pdf.pages)280        281        if not text.strip():282            raise HTTPException(400, "PDF contains no readable text")283        284        chunks = chunk_text(text)285        if not chunks:286            raise HTTPException(400, "Could not extract meaningful text from PDF")287        288        embeddings = embedder.encode(chunks)289        file_id = str(uuid.uuid4())290 291        document_collection.add(292            ids=[f"{file_id}_{i}" for i in range(len(chunks))],293            embeddings=embeddings.tolist(),294            documents=chunks,295            metadatas=[{"file_id": file_id, "filename": file.filename} for _ in chunks]296        )297 298        return {299            "file_id": file_id,300            "filename": file.filename,301            "status": "success",302            "chunks": len(chunks)303        }304    except Exception as e:305        logger.error(f"Upload error: {e}")306        raise HTTPException(500, f"Failed to process PDF: {str(e)}")307 308@app.post("/ask")309async def ask_question(question: str = Form(...), file_id: Optional[str] = Form(None)):310    try:311        if not question.strip():312            raise HTTPException(400, "Question cannot be empty")313            314        question_embedding = embedder.encode(question).tolist()315        316        query_params = {"query_embeddings": [question_embedding], "n_results": 3}317        if file_id:318            query_params["where"] = {"file_id": file_id}319            320        results = document_collection.query(321            **query_params,322            include=["documents", "distances", "metadatas"]323        )324 325        answers = []326        if results['documents']:327            for doc, dist, meta in zip(results['documents'][0], results['distances'][0], results['metadatas'][0]):328                answers.append({329                    "text": doc,330                    "similarity": float(1 - dist),331                    "source": "document",332                    "filename": meta.get("filename", "Unknown")333                })334 335        # For definition questions, use dictionary service336        if not answers and ("definition" in question.lower() or "define" in question.lower() or "what is" in question.lower()):337            # Extract word from question338            words = re.findall(r"['\"](.*?)['\"]", question)339            if not words:340                # Try to extract word from "what is X" pattern341                match = re.search(r'(?:what is|define|definition of)\s+([a-zA-Z]+)', question.lower())342                if match:343                    words = [match.group(1)]344            345            if words:346                word = words[0].lower()347                definitions = dict_service.get_definitions(word)348                if definitions:349                    answers.append({350                        "text": "\n".join([f"{i+1}. {defn}" for i, defn in enumerate(definitions)]),351                        "similarity": 1.0,352                        "source": "dictionary",353                        "filename": f"Definition of {word}"354                    })355 356        if not answers:357            answers.append({358                "text": "No relevant information found in the documents. Try rephrasing your question or check the dictionary feature for definitions.",359                "similarity": 0.0,360                "source": "system",361                "filename": "Help"362            })363 364        return {"answers": answers}365    except Exception as e:366        logger.error(f"Question error: {e}")367        raise HTTPException(500, f"Failed to get answer: {str(e)}")368 369@app.get("/dictionary/{word}")370async def dictionary_lookup(word: str):371    try:372        if not word.strip():373            raise HTTPException(400, "Word cannot be empty")374        375        logger.info(f"Dictionary lookup for: {word}")376        377        # Get definitions from reliable sources378        definitions = dict_service.get_definitions(word)379        380        if definitions:381            return {382                "word": word,383                "definitions": definitions,384                "source": "Free Dictionary API + Merriam-Webster",385                "sources_used": len(definitions)386            }387        else:388            # Provide helpful fallback for common words389            common_words = {390                "document": [391                    "A written or printed record that provides information or serves as an official record",392                    "A computer file containing text, images, or other data",393                    "To record or report something in detail, typically in writing"394                ],395                "computer": [396                    "An electronic device for storing and processing data, typically in binary form",397                    "A machine that can be instructed to carry out sequences of arithmetic or logical operations automatically"398                ],399                "python": [400                    "A high-level programming language known for its readability and versatility, used for web development, data analysis, and artificial intelligence",401                    "A large constricting snake found in tropical regions of Africa, Asia, and Australia"402                ],403                "ai": [404                    "Artificial Intelligence: The simulation of human intelligence processes by machines, especially computer systems",405                    "The capability of a machine to imitate intelligent human behavior and perform tasks that typically require human intelligence"406                ],407                "cat": [408                    "A small domesticated carnivorous mammal with soft fur, a short snout, and retractable claws",409                    "Any member of the family Felidae, including lions, tigers, and leopards"410                ],411                "dog": [412                    "A domesticated carnivorous mammal that typically has a long snout, an acute sense of smell, and a barking, howling, or whining voice",413                    "A member of the canine family, often kept as a pet or used for hunting, guarding, or assisting"414                ]415            }416            417            if word.lower() in common_words:418                return {419                    "word": word,420                    "definitions": common_words[word.lower()],421                    "source": "Common Definitions",422                    "sources_used": 1423                }424            else:425                return {426                    "word": word,427                    "definitions": [428                        f"No definitions found for '{word}'.",429                        "This might be because:",430                        "• The word is very uncommon or specialized",431                        "• There might be a temporary network issue",432                        "• The word might be misspelled",433                        "Please try checking the spelling or try a different word."434                    ],435                    "source": "No definitions available",436                    "sources_used": 0437                }438            439    except Exception as e:440        logger.error(f"Dictionary error: {e}")441        return {442            "word": word,443            "definitions": [f"Error retrieving definitions: {str(e)}"],444            "source": "Error",445            "sources_used": 0446        }447 448@app.get("/summarize/{file_id}")449async def summarize_document(file_id: str):450    try:451        results = document_collection.get(452            where={"file_id": file_id},453            include=["documents", "metadatas"]454        )455        456        if not results['documents']:457            raise HTTPException(404, "Document not found")458        459        full_text = " ".join(results['documents'])460        filename = results['metadatas'][0]['filename'] if results['metadatas'] else "Unknown"461        462        summary = create_local_summary(full_text)463        464        return {465            "file_id": file_id,466            "filename": filename,467            "summary": summary.strip(),468            "source": "Local Processing"469        }470    except Exception as e:471        logger.error(f"Summarization error: {e}")472        raise HTTPException(500, f"Failed to summarize document: {str(e)}")473 474@app.get("/files")475async def list_files():476    try:477        results = document_collection.get()478        files = {}479        for metadata in results['metadatas']:480            file_id = metadata['file_id']481            if file_id not in files:482                files[file_id] = metadata['filename']483        484        return {"files": [{"id": k, "name": v} for k, v in files.items()]}485    except Exception as e:486        logger.error(f"File list error: {e}")487        return {"files": []}488 489@app.get("/health")490async def health_check():491    return {492        "status": "healthy",493        "dictionary_service": "active",494        "embedding_model": EMBEDDING_MODEL,495        "message": "Dictionary service is running with Free Dictionary API"496    }497 498@app.get("/test-dictionary/{word}")499async def test_dictionary(word: str):500    """Test the dictionary service"""501    definitions = dict_service.get_definitions(word)502    return {503        "word": word,504        "definitions_found": len(definitions),505        "definitions": definitions,506        "status": "success" if definitions else "no_definitions"507    }508if __name__ == "__main__":509    import uvicorn510    port = int(os.environ.get("PORT", 7860))  # Use HF Space port511    uvicorn.run("backend:app", host="0.0.0.0", port=port)512