#!/usr/bin/env python3
"""
PDF Chat Web Application - Hugging Face Spaces Version
======================================================
A Streamlit web app for chatting with PDF documents using OpenAI.
Deployed on Hugging Face Spaces for public access.
"""
import streamlit as st
import os
import tempfile
import PyPDF2
from io import BytesIO
import requests
import json
# Page configuration
st.set_page_config(
page_title="PDF Chat Assistant",
page_icon="đ",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for modern dark theme
st.markdown("""
""", unsafe_allow_html=True)
class PDFChatBot:
def __init__(self):
self.pdf_text = ""
self.conversation_history = []
self.pdf_pages = 0
self.pdf_chars = 0
def extract_pdf_text(self, pdf_file):
"""Extract text from PDF using PyPDF2"""
try:
pdf_reader = PyPDF2.PdfReader(pdf_file)
text = ""
page_count = len(pdf_reader.pages)
for page in pdf_reader.pages:
text += page.extract_text() + "\n"
if not text.strip():
return False, "Could not extract text from PDF. The PDF might contain only images or be password protected."
self.pdf_text = text
self.pdf_pages = page_count
self.pdf_chars = len(text)
return True, f"Successfully extracted text from {page_count} pages ({len(text):,} characters)!"
except Exception as e:
return False, f"Error reading PDF: {str(e)}"
def ask_openai(self, question, api_key):
"""Ask OpenAI directly using the API"""
try:
# Limit context to prevent token limits
context_limit = 3000
context = f"Based on the following document content, please answer the question accurately and concisely.\n\nDocument:\n{self.pdf_text[:context_limit]}\n\nQuestion: {question}"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {
"model": "gpt-3.5-turbo",
"messages": [
{"role": "system", "content": "You are a helpful AI assistant that answers questions about documents. Be accurate, concise, and helpful. If you cannot find the answer in the document, say so clearly."},
{"role": "user", "content": context}
],
"max_tokens": 1000,
"temperature": 0.1
}
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
json=data,
timeout=30
)
if response.status_code == 200:
result = response.json()
answer = result['choices'][0]['message']['content']
# Store in conversation history
self.conversation_history.append({"question": question, "answer": answer})
return answer
elif response.status_code == 401:
return "â Invalid API key. Please check your OpenAI API key and try again."
elif response.status_code == 429:
return "âŗ Rate limit exceeded. Please wait a moment and try again."
else:
return f"â API Error: {response.status_code} - Please check your API key and try again."
except requests.exceptions.Timeout:
return "âŗ Request timed out. Please try again."
except requests.exceptions.ConnectionError:
return "đ Connection error. Please check your internet connection."
except Exception as e:
return f"â Error: {str(e)}"
def main():
# Initialize session state
if 'bot' not in st.session_state:
st.session_state.bot = PDFChatBot()
if 'messages' not in st.session_state:
st.session_state.messages = []
if 'pdf_processed' not in st.session_state:
st.session_state.pdf_processed = False
if 'uploaded_file_name' not in st.session_state:
st.session_state.uploaded_file_name = ""
# Header
st.markdown("""
đ PDF Chat Assistant
Upload any PDF and start an intelligent conversation with your document!
Powered by OpenAI GPT âĸ Built with â¤ī¸ for Hugging Face Spaces
""", unsafe_allow_html=True)
# Sidebar
with st.sidebar:
st.markdown("### đ Configuration")
api_key = st.text_input(
"OpenAI API Key",
type="password",
help="Enter your OpenAI API key to start chatting with PDFs",
placeholder="sk-..."
)
if api_key:
st.success("â
API Key Provided")
else:
st.error("â API Key Required")
st.markdown("---")
st.markdown("### đ How to Use")
st.markdown("""
1. **đ Enter your OpenAI API key** above
2. **đ¤ Upload a PDF file** using the uploader
3. **âŗ Wait** for text extraction (few seconds)
4. **đŦ Ask questions** about your document
5. **đ§ Get AI-powered answers** instantly!
""")
st.markdown("---")
st.markdown("### đ¯ Features")
st.markdown("""
âĸ đ **PDF Text Extraction**
âĸ đ¤ **AI-Powered Q&A**
âĸ đž **Conversation Memory**
âĸ đ¨ **Beautiful Interface**
âĸ đ **Fast & Responsive**
âĸ đ **Privacy Focused**
""")
st.markdown("---")
if st.button("đī¸ Clear Chat History", use_container_width=True):
st.session_state.messages = []
st.session_state.bot = PDFChatBot()
st.session_state.pdf_processed = False
st.session_state.uploaded_file_name = ""
st.success("â
Chat history cleared!")
st.rerun()
# API Key Info
with st.expander("âšī¸ Get OpenAI API Key"):
st.markdown("""
**How to get your API key:**
1. Go to [OpenAI Platform](https://platform.openai.com)
2. Sign up or log in to your account
3. Navigate to **API Keys** section
4. Click **"Create new secret key"**
5. Copy the key (starts with `sk-`)
6. Paste it in the field above
**Note:** Your API key is only used for this session and is not stored anywhere.
""")
# Status
st.markdown("---")
st.markdown("### đ Status")
if st.session_state.pdf_processed:
st.success("â
PDF Ready")
st.info(f"đ {st.session_state.uploaded_file_name}")
# Display PDF stats
bot = st.session_state.bot
st.markdown(f"""
**đ Document Stats:**
- Pages: {bot.pdf_pages}
- Characters: {bot.pdf_chars:,}
- Conversations: {len(bot.conversation_history)}
""")
else:
st.warning("âŗ No PDF loaded")
if api_key:
st.success("â
API Connected")
else:
st.error("â API Key Missing")
# Main content
col1, col2 = st.columns([1, 2])
with col1:
st.markdown("### đ¤ Upload Your PDF")
uploaded_file = st.file_uploader(
"Choose a PDF file",
type="pdf",
help="Upload any PDF document (max 200MB)",
label_visibility="collapsed"
)
if not uploaded_file:
st.markdown("""
đ Drag & Drop Your PDF Here
Or click "Browse files" above to select a PDF
đ Supported: PDF files up to 200MB
đ Your files are processed securely and not stored
""", unsafe_allow_html=True)
if uploaded_file and api_key:
if not st.session_state.pdf_processed or st.session_state.uploaded_file_name != uploaded_file.name:
with st.spinner("đ Extracting text from your PDF..."):
success, message = st.session_state.bot.extract_pdf_text(uploaded_file)
if success:
st.markdown(f"""
â
PDF Processed Successfully!
{message}
""", unsafe_allow_html=True)
st.session_state.pdf_processed = True
st.session_state.uploaded_file_name = uploaded_file.name
# Show file details
file_size = uploaded_file.size / 1024 # KB
bot = st.session_state.bot
st.markdown(f"""
đ File: {uploaded_file.name}
đ Size: {file_size:.1f} KB
đ Pages: {bot.pdf_pages}
đ Characters: {bot.pdf_chars:,}
đ¯ Status: Ready for questions!
""", unsafe_allow_html=True)
else:
st.markdown(f"""
â ī¸ Processing Failed
{message}
""", unsafe_allow_html=True)
elif uploaded_file and not api_key:
st.markdown("""
â ī¸ API Key Required
Please enter your OpenAI API key in the sidebar to process the PDF.
""", unsafe_allow_html=True)
# Example questions
if st.session_state.pdf_processed:
st.markdown("### đĄ Try These Questions")
example_questions = [
"đ What is this document about?",
"đ Summarize the main points",
"đ What are the key details?",
"đ Give me important information",
"â What questions can I ask?"
]
for question in example_questions:
if st.button(question, key=f"example_{question}", use_container_width=True):
# Trigger the question
question_text = question.split(" ", 1)[1] # Remove emoji
st.session_state.pending_question = question_text
st.rerun()
with col2:
st.markdown("### đŦ Chat with Your PDF")
# Chat container
chat_container = st.container()
with chat_container:
if st.session_state.messages:
for message in st.session_state.messages:
if message["role"] == "user":
st.markdown(f"""
đ§ You: {message["content"]}
""", unsafe_allow_html=True)
else:
st.markdown(f"""
đ¤ AI: {message["content"]}
""", unsafe_allow_html=True)
else:
if st.session_state.pdf_processed:
st.markdown("""
đ¤ AI: Hello! I've analyzed your PDF document. What would you like to know about it? Feel free to ask any questions!
""", unsafe_allow_html=True)
else:
st.markdown("""
đ Welcome to PDF Chat Assistant!
Transform any PDF into an interactive conversation
đ Smart
AI understands your documents
⥠Fast
Instant answers to your questions
đ Secure
Your data stays private
Get started: Add your API key and upload a PDF!
""", unsafe_allow_html=True)
# Input area
st.markdown("---")
# Check for pending question from example buttons
if hasattr(st.session_state, 'pending_question'):
user_question = st.session_state.pending_question
del st.session_state.pending_question
else:
user_question = st.text_input(
"Ask a question about your PDF:",
placeholder="e.g., What are the main topics discussed in this document?",
disabled=not st.session_state.pdf_processed,
key="user_input"
)
col_btn1, col_btn2, col_btn3 = st.columns([2, 1, 1])
with col_btn1:
send_button = st.button("đ¤ Send Message", disabled=not st.session_state.pdf_processed, use_container_width=True)
with col_btn2:
if st.button("đ Refresh", disabled=not st.session_state.pdf_processed, use_container_width=True):
st.rerun()
# Process question
if (send_button or hasattr(st.session_state, 'pending_question')) and user_question and st.session_state.pdf_processed and api_key:
# Add user message
st.session_state.messages.append({"role": "user", "content": user_question})
# Get AI response
with st.spinner("đ¤ AI is analyzing your question..."):
ai_response = st.session_state.bot.ask_openai(user_question, api_key)
# Add AI response
st.session_state.messages.append({"role": "assistant", "content": ai_response})
st.rerun()
elif send_button and not st.session_state.pdf_processed:
st.warning("â ī¸ Please upload and process a PDF first!")
elif send_button and not api_key:
st.warning("â ī¸ Please enter your OpenAI API key in the sidebar!")
# Footer
st.markdown("---")
st.markdown("""
đ PDF Chat Assistant
Made with â¤ī¸ using Streamlit âĸ Powered by OpenAI GPT-3.5 âĸ Hosted on đ¤ Hugging Face Spaces
đ Upload PDFs âĸ đŦ Ask Questions âĸ đ§ Get AI Answers âĸ đ Privacy First
â Like this app? Give it a star on Hugging Face Spaces!
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()