Download app.py from CaseLoop/pdf: direct link, hf CLI and curl.
- Browser
- Download file 3.36 kB
-
https://huggingface.co/CaseLoop/pdf/resolve/a92f235ea2c574cd6413ec0030b67fa5fa7c6b1a/app.py
- Command line
-
hf download hf://CaseLoop/pdf@a92f235ea2c574cd6413ec0030b67fa5fa7c6b1a/app.py
-
curl -L -o app.py https://huggingface.co/CaseLoop/pdf/resolve/a92f235ea2c574cd6413ec0030b67fa5fa7c6b1a/app.py
3.36 kB
| import os | |
| import tempfile | |
| import fitz # PyMuPDF | |
| import streamlit as st | |
| import torch | |
| from transformers import AutoConfig, AutoTokenizer, T5ForConditionalGeneration | |
| # Page Configuration | |
| st.set_page_config( | |
| page_title="RTI & Civic PDF Simplifier", | |
| page_icon="📄", | |
| layout="centered", | |
| ) | |
| st.title("📄 Civic & RTI PDF Simplifier") | |
| st.markdown( | |
| "Upload bureaucratic, legal, or RTI PDF documents to generate concise," | |
| " plain-language bullet points." | |
| ) | |
| MODEL_REPO = "CaseLoop/pdf" | |
| FALLBACK_BASE = "google/flan-t5-base" | |
| def load_model_and_tokenizer(): | |
| # Tokenizer fallback | |
| try: | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_REPO, legacy=False) | |
| except Exception: | |
| tokenizer = AutoTokenizer.from_pretrained(FALLBACK_BASE, legacy=False) | |
| # Model loader with architecture fallback | |
| try: | |
| model = T5ForConditionalGeneration.from_pretrained(MODEL_REPO) | |
| except Exception: | |
| try: | |
| config = AutoConfig.from_pretrained(FALLBACK_BASE) | |
| model = T5ForConditionalGeneration.from_pretrained( | |
| MODEL_REPO, config=config | |
| ) | |
| except Exception: | |
| model = T5ForConditionalGeneration.from_pretrained(FALLBACK_BASE) | |
| model.eval() | |
| return tokenizer, model | |
| tokenizer, model = load_model_and_tokenizer() | |
| def extract_chunks( | |
| pdf_path: str, chunk_size: int = 180, max_chunks: int = 6 | |
| ) -> list[str]: | |
| """Extracts lines from a PDF and chunks words for FLAN-T5 input limits.""" | |
| doc = fitz.open(pdf_path) | |
| lines = [] | |
| for page in doc: | |
| text = page.get_text("text") | |
| lines.extend([ | |
| line.strip() | |
| for line in text.split("\n") | |
| if len(line.strip().split()) >= 5 | |
| ]) | |
| words = " ".join(lines).split() | |
| if not words: | |
| return [] | |
| chunks = [ | |
| " ".join(words[i : i + chunk_size]) | |
| for i in range(0, len(words), chunk_size) | |
| ] | |
| return chunks[:max_chunks] | |
| # UI File Uploader | |
| uploaded_file = st.file_uploader( | |
| "Choose a PDF file", type=["pdf"], help="Upload an RTI reply or civic notice" | |
| ) | |
| if uploaded_file is not None: | |
| if st.button("Simplify Document", type="primary"): | |
| with st.spinner("Parsing document and running simplification..."): | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: | |
| tmp.write(uploaded_file.read()) | |
| tmp_path = tmp.name | |
| chunks = extract_chunks(tmp_path) | |
| if os.path.exists(tmp_path): | |
| os.remove(tmp_path) | |
| if not chunks: | |
| st.warning( | |
| "No extractable text found in this PDF. It might be a scanned image." | |
| ) | |
| else: | |
| st.subheader("Key Takeaways in Plain Language:") | |
| for idx, chunk in enumerate(chunks, 1): | |
| prompt = f"simplify bureaucratic text into simple language: {chunk}" | |
| inputs = tokenizer( | |
| prompt, return_tensors="pt", max_length=512, truncation=True | |
| ) | |
| with torch.no_grad(): | |
| output_tokens = model.generate( | |
| **inputs, | |
| max_length=120, | |
| min_length=20, | |
| num_beams=4, | |
| no_repeat_ngram_size=3, | |
| repetition_penalty=2.5, | |
| early_stopping=True, | |
| ) | |
| summary = tokenizer.decode(output_tokens[0], skip_special_tokens=True) | |
| st.markdown(f"**{idx}.** {summary}") |