this is my app.py code along with the document_summarizer.html

"

"
from flask import Flask, render_template, request, jsonify, send_file
import google.generativeai as genai
from dotenv import load_dotenv
import os
from werkzeug.utils import secure_filename
from PIL import Image
import PIL
import io
from io import BytesIO
import logging
from langdetect import detect
import requests
from requests import get
from google.generativeai.types import GenerationConfig
from markdown import markdown
from google.generativeai.types import GenerationConfig, HarmCategory, HarmBlockThreshold
import re
import json
from mailjet_rest import Client
from pdf2image import convert_from_bytes
from docx import Document
from docx.shared import Inches
import tempfile
import PyPDF2
import base64
from docx import Document
from docx.shared import Inches, Pt
from docx.enum.style import WD_STYLE_TYPE
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import RGBColor
import fitz # PyMuPDF
import markdown
from bs4 import BeautifulSoup, NavigableString

Firebase imports
import firebase_admin
from firebase_admin import credentials, firestore, storage

from docx import Document
from docx.shared import RGBColor, Inches, Pt
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
import google.generativeai as genai
from google.generativeai.types import GenerationConfig
from google.generativeai.types import HarmCategory, HarmBlockThreshold
import google.api_core.exceptions

from dotenv import load_dotenv
from werkzeug.utils import secure_filename
import fitz # PyMuPDF
import time
import uuid
import base64
from threading import Lock

app = Flask(name)

Load environment variables
load_dotenv()
mail_API_KEY = os.environ.get("mail_API_KEY") # Replace with your Mailjet API key
mail_API_SECRET = os.environ.get("mail_API_SECRET") # Replace with your Mailjet API secret
mailjet = Client(auth=(mail_API_KEY, mail_API_SECRET), version='v3.1')
api_key = os.environ.get("API_KEY")
unsplash_api_key = os.getenv('UNSPLASH_API_KEY')
API_KEY = os.getenv('OPENWEATHERMAP_API_KEY')

Set up logging
logging.basicConfig(level=logging.INFO)

Configure the Google Generative AI API
genai.configure(api_key=api_key)
#session key

user_data = {} # Dictionary to store user data

FIREBASE_TYPE = os.environ.get("FIREBASE_TYPE")
FIREBASE_PROJECT_ID = os.environ.get("FIREBASE_PROJECT_ID")
FIREBASE_PRIVATE_KEY_ID = os.environ.get("FIREBASE_PRIVATE_KEY_ID")
FIREBASE_PRIVATE_KEY = os.environ.get("FIREBASE_PRIVATE_KEY") # Important to handle newlines correctly here
FIREBASE_CLIENT_EMAIL = os.environ.get("FIREBASE_CLIENT_EMAIL")
FIREBASE_CLIENT_ID = os.environ.get("FIREBASE_CLIENT_ID")
FIREBASE_AUTH_URI = os.environ.get("FIREBASE_AUTH_URI")
FIREBASE_TOKEN_URI = os.environ.get("FIREBASE_TOKEN_URI")
FIREBASE_AUTH_PROVIDER_X509_CERT_URL = os.environ.get("FIREBASE_AUTH_PROVIDER_X509_CERT_URL")
FIREBASE_CLIENT_X509_CERT_URL = os.environ.get("FIREBASE_CLIENT_X509_CERT_URL")
FIREBASE_UNIVERSE_DOMAIN = os.environ.get("FIREBASE_UNIVERSE_DOMAIN")

STORAGE_BUCKET_URL = os.environ.get("STORAGE_BUCKET_URL") # Bucket URL

cred = credentials.Certificate({
"type": FIREBASE_TYPE,
"project_id": FIREBASE_PROJECT_ID,
"private_key_id": FIREBASE_PRIVATE_KEY_ID,
"private_key": FIREBASE_PRIVATE_KEY.replace("\n", "\n"), # decode the newlines
"client_email": FIREBASE_CLIENT_EMAIL,
"client_id": FIREBASE_CLIENT_ID,
"auth_uri": FIREBASE_AUTH_URI,
"token_uri": FIREBASE_TOKEN_URI,
"auth_provider_x509_cert_url": FIREBASE_AUTH_PROVIDER_X509_CERT_URL,
"client_x509_cert_url": FIREBASE_CLIENT_X509_CERT_URL,
"universe_domain": FIREBASE_UNIVERSE_DOMAIN
})

firebase_admin.initialize_app(cred, {'storageBucket': STORAGE_BUCKET_URL})
db = firestore.client()
bucket = storage.bucket()

Generation configurations
generation_config = GenerationConfig(
temperature=0.9,
top_p=1,
top_k=1,
max_output_tokens=2048,
candidate_count=1 # Explicitly set to 1 as per documentation
)
generation_config_health = GenerationConfig(
temperature=0.7,
top_p=1,
top_k=1,
max_output_tokens=2048,
candidate_count=1 # Explicitly set to 1 as per documentation
)

Safety Settings
safety_settings = {
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
# ... add other harm categories as needed with BLOCK_NONE
}
logging.basicConfig(level=logging.INFO)

Create model instances (using the same config for now)
chat_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
chef_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
story_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
psychology_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
code_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
algorithm_model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
model_vision = genai.GenerativeModel('gemini-1.5-flash',generation_config=generation_config_health)
model_text = genai.GenerativeModel('gemini-pro',generation_config=generation_config_health)
model = genai.GenerativeModel('gemini-1.5-flash') # Model for flowchart generation

def format_response(response_text):
"""Formats the response text for display."""
lines = [line.strip() for line in response_text.split('\n') if line.strip()]
formatted_text = '
'.join(lines)
return formatted_text

@app.route('/')
def index():
return render_template('index.html')

@app.route('/api/weather')
def get_weather():
user_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
ip_api_url = f"http://ip-api.com/json/{user_ip}"
ip_api_response = requests.get(ip_api_url)


if ip_api_response.status_code == 200:
    ip_api_data = ip_api_response.json()
    city = ip_api_data.get('city')
    if not city:
        return jsonify({'error': 'City not found based on IP'}), 404
else:
    return jsonify({'error': 'Failed to get location from IP'}), 404

url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric"
response = requests.get(url)

if response.status_code == 200:
    data = response.json()
    weather = {
        'city': data['name'],
        'temperature': data['main']['temp'],
        'description': data['weather'][0]['description'] if 'weather' in data and len(data['weather']) > 0 else 'N/A',
        'icon': f"http://openweathermap.org/img/wn/{data['weather'][0]['icon']}@2x.png" if 'weather' in data and len(data['weather']) > 0 else 'N/A'
    }
    return jsonify(weather)
else:
    return jsonify({'error': 'City not found or API request failed'}), 404
@app.route('/fetch_image')
def fetch_image():
genre = request.args.get('genre', 'recipe')
url = f"https://api.unsplash.com/photos/random?query={genre}&client_id={unsplash_api_key}&w=1920&h=1080"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
image_url = data['urls']['regular']
return jsonify({'image_url': image_url})
else:
return jsonify({'error': 'Failed to fetch image'}), 500


@app.route('/send-email', methods=['POST'])
def send_email():
data = request.json
name = data.get('name')
email = data.get('email')
message = data.get('message')


mail_data = {
    'Messages': [
        {
            "From": {
                "Email": "21131A05C6@gvpce.ac.in",  # Replace with your email
                "Name": "Kv Nexus"
            },
            "To": [
                {
                    "Email": "21131A05C6@gvpce.ac.in",  # Replace with your email
                    "Name": "Kv Nexus"
                }
            ],
            "Subject": f"New Contact Form Submission from {name}",
            "TextPart": f"Name: {name}\nEmail: {email}\nMessage: {message}",
            "HTMLPart": f"<h3>New Contact Form Submission</h3><p><strong>Name:</strong> {name}</p><p><strong>Email:</strong> {email}</p><p><strong>Message:</strong> {message}</p>"
        }
    ]
}

result = mailjet.send.create(data=mail_data)

if result.status_code == 200:
    return jsonify({"message": "Email sent successfully!"}), 200
else:
    return jsonify({"message": "Failed to send email."}), 500
#docuement summarize

Rate limiting parameters
REQUEST_LIMIT = 15 # Max requests per time window
TIME_WINDOW = 60 # Time window in seconds
rate_limit_lock = Lock()
last_reset_time = time.time()
request_count = 0

def rate_limited(func):
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
global request_count, last_reset_time
with rate_limit_lock:
current_time = time.time()
if current_time - last_reset_time >= TIME_WINDOW:
request_count = 0
last_reset_time = current_time
if request_count >= REQUEST_LIMIT:
remaining_time = TIME_WINDOW - (current_time - last_reset_time)
return jsonify({
'error': f'Rate limit exceeded. Please try again in {int(remaining_time)} seconds.'
}), 429
request_count += 1
return func(*args, **kwargs)
return wrapper

@app.route('/document_summarizer', methods=['GET', 'POST'])
def document_summarizer():
return render_template('document_summarizer.html')

@app.route('/quote', methods=['GET'])
def get_quote():
import random
quotes = [
"The best way to predict the future is to invent it. – Alan Kay",
"Life is like riding a bicycle. To keep your balance you must keep moving. – Albert Einstein",
"Problems are not stop signs, they are guidelines. – Robert H. Schuller",
"In order to succeed, we must first believe that we can. – Nikos Kazantzakis",
"The only limit to our realization of tomorrow is our doubts of today. – Franklin D. Roosevelt"
]
quote = random.choice(quotes)
return jsonify({'quote': quote})

def generate_summary(texts, images, max_retries=3):
prompt = [
"""Summarize the following text into a concise and simplified summary. Ensure the summary is well-structured with clear headings and subheadings.

Formatting Guidelines:

Use # for main section titles.
Use ## for subsections.
Use - for bullet points.
For bold text, wrap the text with double asterisks, e.g., **important**.
For italic text, wrap the text with single asterisks, e.g., *note*.
For tables, use proper Markdown table syntax with pipes | and hyphens - for headers.
Example Table:
Component Function
Node 1 Receive user input
Node 2 Process user input
Node 3 Store processed data
Node 4 Retrieve data for display
Node 5 Display data to user

Keep sentences short and use simple language.
Focus on the main ideas and avoid unnecessary details.
Do not include direct error messages or irrelevant information.
Here is the text to summarize:
""",
"\n".join(texts)
]
for img in images:
prompt.append(img)
retries = 0
backoff_time = 10 # Start with 10 seconds
while retries < max_retries:
try:
response = model_vision.generate_content(prompt, safety_settings=safety_settings)
summary_text = response.text
# Log the AI model output for debugging
print("AI Model Output:\n", summary_text)
return summary_text
except google.api_core.exceptions.ResourceExhausted as e:
print(f"Resource exhausted: {e}. Retrying after {backoff_time} seconds...")
time.sleep(backoff_time)
retries += 1
backoff_time *= 2 # Exponential backoff
except Exception as e:
print(f"Error in Gemini API call: {e}")
return None # Return None to indicate failure
# If we exhaust retries, handle accordingly
print("Failed to generate summary after retries.")
return None

def create_word_document(summary):
doc = Document()


# Define styles
define_custom_styles(doc)

# Adjust document layout
adjust_document_layout(doc)

# Set page borders
set_page_border(doc)

# Convert Markdown to HTML with necessary extensions
html = markdown.markdown(summary, extensions=['extra', 'tables', 'fenced_code', 'codehilite', 'nl2br'])

# Parse HTML
soup = BeautifulSoup(html, 'html.parser')

# Iterate over HTML elements and add them to the Word document
for element in soup.contents:
    process_html_element(doc, element)

docx_buffer = io.BytesIO()
doc.save(docx_buffer)
docx_buffer.seek(0)
return docx_buffer
def define_custom_styles(doc):
styles = doc.styles


# Title Style (Heading 1)
style_h1 = styles['Heading 1']
style_h1.font.name = 'Calibri Light'
style_h1.font.size = Pt(24)
style_h1.font.bold = True
style_h1.font.color.rgb = RGBColor(31, 56, 100)
style_h1.paragraph_format.space_after = Pt(12)
style_h1.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER

# Subtitle Style (Heading 2)
style_h2 = styles['Heading 2']
style_h2.font.name = 'Calibri'
style_h2.font.size = Pt(20)
style_h2.font.bold = True
style_h2.font.color.rgb = RGBColor(54, 95, 145)
style_h2.paragraph_format.space_before = Pt(12)
style_h2.paragraph_format.space_after = Pt(6)

# Normal Text Style
style_normal = styles['Normal']
style_normal.font.name = 'Calibri'
style_normal.font.size = Pt(12)
style_normal.paragraph_format.space_after = Pt(8)
style_normal.paragraph_format.line_spacing = 1.15

# List Bullet Style
style_list_bullet = styles['List Bullet']
style_list_bullet.font.name = 'Calibri'
style_list_bullet.font.size = Pt(12)
style_list_bullet.paragraph_format.space_after = Pt(4)
style_list_bullet.paragraph_format.line_spacing = 1.15

# List Number Style
style_list_number = styles['List Number']
style_list_number.font.name = 'Calibri'
style_list_number.font.size = Pt(12)
style_list_number.paragraph_format.space_after = Pt(4)
style_list_number.paragraph_format.line_spacing = 1.15
def adjust_document_layout(doc):
section = doc.sections[0]
section.page_height = Inches(11)
section.page_width = Inches(8.5)
section.left_margin = Inches(1)
section.right_margin = Inches(1)
section.top_margin = Inches(1)
section.bottom_margin = Inches(1)

def set_page_border(doc):
for section in doc.sections:
sectPr = section._sectPr
pgBorders = OxmlElement('w:pgBorders')
pgBorders.set(qn('w:offsetFrom'), 'page')
for border_position in ('top', 'left', 'bottom', 'right'):
border_el = OxmlElement(f'w:{border_position}')
border_el.set(qn('w:val'), 'single')
border_el.set(qn('w:sz'), '24')
border_el.set(qn('w:space'), '24')
border_el.set(qn('w:color'), '5B9BD5')
pgBorders.append(border_el)
sectPr.append(pgBorders)

def process_html_element(doc, element, parent=None):
if isinstance(element, NavigableString):
text = str(element)
if text.strip():
if parent is not None:
parent.add_run(text)
else:
p = doc.add_paragraph()
p.add_run(text)
return
elif element.name is not None:
if element.name == 'h1':
p = doc.add_heading(level=1)
add_runs_from_element(p, element)
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
elif element.name == 'h2':
p = doc.add_heading(level=2)
add_runs_from_element(p, element)
elif element.name == 'p':
p = doc.add_paragraph()
add_runs_from_element(p, element)
elif element.name in ['strong', 'b']:
if parent is not None:
run = parent.add_run(element.get_text())
run.bold = True
else:
p = doc.add_paragraph()
run = p.add_run(element.get_text())
run.bold = True
elif element.name in ['em', 'i']:
if parent is not None:
run = parent.add_run(element.get_text())
run.italic = True
else:
p = doc.add_paragraph()
run = p.add_run(element.get_text())
run.italic = True
elif element.name == 'ul':
for li in element.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Bullet')
add_runs_from_element(p, li)
elif element.name == 'ol':
for li in element.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Number')
add_runs_from_element(p, li)
elif element.name == 'table':
add_table_to_document_from_html(doc, element)
else:
# Process children
for child in element.contents:
process_html_element(doc, child, parent)
else:
# Unknown element type
pass

def add_runs_from_element(paragraph, element):
if isinstance(element, NavigableString):
text = str(element)
if text.strip():
paragraph.add_run(text)
elif element.name is not None:
if element.name in ['strong', 'b']:
run = paragraph.add_run(element.get_text())
run.bold = True
elif element.name in ['em', 'i']:
run = paragraph.add_run(element.get_text())
run.italic = True
else:
for content in element.contents:
add_runs_from_element(paragraph, content)
else:
# Unknown element type
pass

def add_table_to_document_from_html(doc, table_element):
rows = table_element.find_all('tr')
if not rows:
return


num_cols = len(rows[0].find_all(['td', 'th']))
table = doc.add_table(rows=0, cols=num_cols)
table.style = 'Table Grid'  # Or define a custom table style

for row_idx, row in enumerate(rows):
    cells = row.find_all(['td', 'th'])
    row_cells = table.add_row().cells
    for idx, cell in enumerate(cells):
        cell_text = cell.get_text(strip=True)
        paragraph = row_cells[idx].paragraphs[0]
        paragraph.clear()  # Clear existing content
        run = paragraph.add_run(cell_text)
        row_cells[idx].vertical_alignment = WD_ALIGN_VERTICAL.CENTER
        # Apply styling for header cells
        if cell.name == 'th':
            run.bold = True
            shading_elm = OxmlElement('w:shd')
            shading_elm.set(qn('w:fill'), 'D9E1F2')  # Light blue background
            row_cells[idx]._tc.get_or_add_tcPr().append(shading_elm)
@app.route('/upload', methods=['POST'])
@rate_limited
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400


if file and file.filename.endswith('.pdf'):
    try:
        # Generate a unique PDF ID
        pdf_id = str(uuid.uuid4())

        # Upload the PDF to Firebase Storage
        blob = bucket.blob(f'pdfs/{pdf_id}.pdf')
        blob.upload_from_file(file)

        # Get the total number of pages
        pdf_document = fitz.open(stream=blob.download_as_bytes(), filetype="pdf")
        total_pages = len(pdf_document)
        pdf_document.close()

        # Initialize processing status in Firestore
        db.collection('pdf_processes').document(pdf_id).set({
            'status': 'processing',
            'current_page': 0,
            'total_pages': total_pages,
            'summary': '',
            'processing_start_time': time.time(),
            'timestamp': firestore.SERVER_TIMESTAMP
        })

        return jsonify({'pdf_id': pdf_id}), 200
    except Exception as e:
        logging.error(f"Error uploading file: {e}")
        return jsonify({'error': str(e)}), 500
else:
    return jsonify({'error': 'Invalid file type. Please upload a PDF.'}), 400
@app.route('/process_pdf', methods=['POST'])
@rate_limited
def process_pdf_endpoint():
data = request.get_json()
pdf_id = data.get('pdf_id')
if not pdf_id:
return jsonify({'error': 'No PDF ID provided.'}), 400


# Retrieve processing status from Firestore
doc_ref = db.collection('pdf_processes').document(pdf_id)
doc = doc_ref.get()
if not doc.exists:
    return jsonify({'error': 'Invalid PDF ID.'}), 400
result = doc.to_dict()

try:
    blob = bucket.blob(f'pdfs/{pdf_id}.pdf')
    if not blob.exists():
        return jsonify({'error': 'File not found.'}), 404

    pdf_bytes = blob.download_as_bytes()
    pdf_document = fitz.open(stream=pdf_bytes, filetype="pdf")
    total_pages = result.get('total_pages', len(pdf_document))

    current_page = result['current_page']
    summary = result['summary']

    if current_page < total_pages:
        page = pdf_document[current_page]
        text = page.get_text()
        images = []

        # Extract images
        image_list = page.get_images(full=True)
        for img_index, img in enumerate(image_list):
            xref = img[0]
            base_image = pdf_document.extract_image(xref)
            image_bytes = base_image["image"]
            image_base64 = base64.b64encode(image_bytes).decode('utf-8')
            images.append(image_base64)

        # Generate summary for the page
        page_summary = generate_summary([text], images)
        if page_summary:
            summary += page_summary + '\n\n'
        else:
            print(f"Failed to generate summary for page {current_page + 1}. Skipping this page.")
            summary += f"(Summary not available for page {current_page + 1})\n\n"

        current_page += 1
        # Update the processing status in Firestore
        doc_ref.update({'current_page': current_page, 'summary': summary})

        pdf_document.close()

        if current_page >= total_pages:
            # Processing complete
            doc_ref.update({'status': 'completed', 'processing_end_time': time.time()})
            # Optionally delete the PDF from storage
            blob.delete()
            return jsonify({'status': 'completed'}), 200
        else:
            # Processing not complete
            return jsonify({'status': 'processing', 'current_page': current_page, 'total_pages': total_pages}), 200
    else:
        # All pages are already processed
        return jsonify({'status': 'completed'}), 200

except Exception as e:
    logging.error(f"Error processing PDF: {e}")
    return jsonify({'error': str(e)}), 500
@app.route('/check_status', methods=['GET'])
def check_status():
pdf_id = request.args.get('pdf_id')
if not pdf_id:
return jsonify({'error': 'No PDF ID provided.'}), 400


# Retrieve processing status from Firestore
doc_ref = db.collection('pdf_processes').document(pdf_id)
doc = doc_ref.get()
if not doc.exists:
    return jsonify({'error': 'Invalid PDF ID.'}), 400
result = doc.to_dict()

status = result.get('status', 'processing')
if status == 'completed':
    # Create word document
    summary = result['summary']
    docx_buffer = create_word_document(summary)
    # Encode the docx file to base64
    docx_base64 = base64.b64encode(docx_buffer.getvalue()).decode('utf-8')

    # Calculate processing time
    processing_start_time = result.get('processing_start_time')
    processing_end_time = result.get('processing_end_time')
    if processing_start_time and processing_end_time:
        processing_time = int(processing_end_time - processing_start_time)
    else:
        processing_time = 'N/A'

    return jsonify({
        'status': 'completed',
        'docx': docx_base64,
        'total_pages': result.get('total_pages', 0),
        'processing_time': processing_time
    }), 200
else:
    return jsonify({
        'status': 'processing',
        'current_page': result.get('current_page', 0),
        'total_pages': result.get('total_pages', 0)
    }), 200
if name == 'main':
app.run(debug=True)
"
"

Quote of the Day
PDF Summarizer
Quickly generate summaries of your PDF documents


        <!-- Upload Form -->
        <form id="upload-form" class="space-y-6" enctype="multipart/form-data">
            <!-- File Upload -->
            <div class="flex items-center justify-center w-full" data-aos="fade-up">
                <label for="file" class="file-upload-label relative flex flex-col items-center justify-center w-full h-48 sm:h-64 border-4 border-dashed rounded-2xl cursor-pointer bg-white hover:bg-gray-50 transition duration-500 ease-in-out transform hover:scale-105 glass-effect">
                    <div class="flex flex-col items-center justify-center">
                        <i class="fas fa-cloud-upload-alt text-5xl sm:text-7xl text-blue-500 mb-4 animate-bounce"></i>
                        <p class="mb-2 text-base sm:text-lg text-gray-700 text-center">
                            <span class="font-semibold">Click to upload</span>
                            or drag and drop
                        </p>
                        <p class="text-sm text-gray-500">
                            PDF files only (Max 10MB)
                        </p>
                    </div>
                    <input id="file" name="file" type="file" class="absolute inset-0 opacity-0 cursor-pointer" accept=".pdf"/>
                </label>
            </div>

            <!-- File Preview -->
            <div id="file-preview" class="hidden bg-gray-50 p-4 rounded-lg border border-gray-200 glass-effect" data-aos="fade-up">
                <h3 class="font-semibold text-gray-700 mb-2">
                    Selected File:
                </h3>
                <div class="flex items-center">
                    <i class="far fa-file-pdf text-red-500 text-2xl mr-2"></i>
                    <span id="file-name" class="text-gray-800"></span>
                </div>
            </div>

            <!-- Submit Button -->
            <button type="submit" class="w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white py-4 px-6 rounded-xl font-bold shadow-lg hover:from-purple-600 hover:to-pink-600 transition duration-500 ease-in-out transform hover:-translate-y-1 hover:scale-105 focus:outline-none focus:ring-4 focus:ring-purple-200 hover-effect" data-aos="zoom-in-up">
                Summarize PDF
            </button>
        </form>

        <!-- Loading Indicator -->
        <div id="loading" class="hidden mt-8 flex flex-col items-center" data-aos="fade-up">
            <div class="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-16 w-16 mb-4"></div>
            <p class="text-lg font-medium text-gray-700 mb-2">
                Processing your PDF, please wait...
            </p>
            <p class="text-sm text-gray-600">
                Processing large PDFs may take several minutes
            </p>
            <p id="progress-text" class="text-sm text-gray-600 mt-2"></p>
        </div>

        <!-- Result Section -->
        <div id="result" class="hidden mt-8 animate-fade-in" data-aos="fade-up">
            <h2 class="text-3xl font-semibold mb-4 text-gray-800">
                Your Summary is Ready!
            </h2>
            <div class="mb-4">
                <p class="text-lg text-gray-700">
                    Total pages processed: <span id="total-pages" class="font-semibold">0</span>
                </p>
                <p class="text-lg text-gray-700">
                    Processing time: <span id="processing-time" class="font-semibold">0</span> seconds
                </p>
            </div>
            <button id="download-button" class="inline-flex items-center bg-green-500 text-white py-3 px-6 rounded-xl font-semibold shadow-lg hover:bg-green-600 transition duration-500 ease-in-out transform hover:-translate-y-1 hover:scale-105 focus:outline-none focus:ring-4 focus:ring-green-200 hover-effect">
                <i class="fas fa-download mr-2"></i> Download Summary
            </button>
        </div>
    </div>
</div>
<!-- AOS JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js"></script>
<!-- Initialize AOS -->
<script>
    AOS.init({
        duration: 1000,
        once: true,
    });
</script>
<script>
    const form = document.getElementById('upload-form');
    const fileUpload = document.getElementById('file');
    const filePreview = document.getElementById('file-preview');
    const fileName = document.getElementById('file-name');
    const loading = document.getElementById('loading');
    const result = document.getElementById('result');
    const downloadButton = document.getElementById('download-button');
    const quoteElement = document.getElementById('quote');
    const progressText = document.getElementById('progress-text');

    // Random Fonts and Colors
    const fonts = [
        'Dancing Script',
        'Kaushan Script',
        'Montserrat',
        'Roboto',
        'Courier New',
        'Georgia',
        'Times New Roman',
    ];
    const colors = [
        '#ff6b6b',
        '#feca57',
        '#48dbfb',
        '#1dd1a1',
        '#5f27cd',
        '#222f3e',
        '#ee5253',
        '#0abde3',
        '#10ac84',
        '#341f97',
    ];

    // Apply Random Font and Color to Quote
    function styleQuote() {
        const randomFont =
            fonts[Math.floor(Math.random() * fonts.length)];
        const randomColor =
            colors[Math.floor(Math.random() * colors.length)];
        quoteElement.style.fontFamily = randomFont;
        quoteElement.style.color = randomColor;
        gsap.from(quoteElement, {
            opacity: 0,
            y: -20,
            duration: 1,
            ease: "bounce.out",
        });
    }

    // Fetch Quote from Server
    async function fetchQuote() {
        try {
            const response = await fetch('/quote');
            if (response.ok) {
                const data = await response.json();
                quoteElement.textContent = `${data.quote}`;
                styleQuote();
            } else {
                quoteElement.textContent =
                    'Error fetching quote. Please try again later.';
            }
        } catch (error) {
            quoteElement.textContent =
                'Error fetching quote. Please try again later.';
        }
    }

    // Load Quote on Page Load
    window.addEventListener('load', fetchQuote);

    // File Upload Preview
    fileUpload.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (file) {
            fileName.textContent = file.name;
            filePreview.classList.remove('hidden');
            gsap.from(filePreview, {
                opacity: 0,
                y: -20,
                duration: 0.5,
            });
        }
    });

    // Form Submission
    form.addEventListener('submit', async (e) => {
        e.preventDefault();
        const formData = new FormData(form);
    
        // Show loading
        form.classList.add('hidden');
        loading.classList.remove('hidden');
        progressText.textContent = 'Uploading file...';
    
        try {
            // Upload the file and get pdf_id
            const response = await fetch('/upload', {
                method: 'POST',
                body: formData,
            });
    
            if (response.ok) {
                const data = await response.json();
                const pdfId = data.pdf_id;

                // Start processing the PDF
                progressText.textContent = 'Processing your PDF...';
                await processPdf(pdfId);
            } else {
                throw new Error('Upload failed');
            }
        } catch (error) {
            console.error('Error:', error);
            alert('An error occurred. Please try again or upload a smaller PDF file.');
            loading.classList.add('hidden');
            form.classList.remove('hidden');
        }
    });
    
    async function processingLoop(pdfId) {
        try {
            const response = await fetch('/process_pdf', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({pdf_id: pdfId}),
            });
    
            if (response.ok) {
                const data = await response.json();
                if (data.status === 'completed') {
                    await checkStatus(pdfId);
                } else if (data.status === 'processing') {
                    progressText.textContent = `Processed ${data.current_page} of ${data.total_pages} pages...`;
                    // Call process_pdf again after a short delay
                    setTimeout(() => processingLoop(pdfId), 1000);
                } else {
                    throw new Error(data.error || 'Processing failed');
                }
            } else {
                throw new Error('Processing failed');
            }
        } catch (error) {
            console.error('Error:', error);
            alert('An error occurred during processing. Please try again later.');
            loading.classList.add('hidden');
            form.classList.remove('hidden');
        }
    }

    // Process PDF
    async function processPdf(pdfId) {
        try {
            // First, check the status
            const statusResponse = await fetch(`/check_status?pdf_id=${pdfId}`);
            if (statusResponse.ok) {
                const statusData = await statusResponse.json();
                if (statusData.status === 'completed') {
                    // If already completed, proceed to download
                    await checkStatus(pdfId);
                } else {
                    // Start the processing loop
                    await processingLoop(pdfId);
                }
            } else {
                throw new Error('Failed to check initial status.');
            }
        } catch (error) {
            console.error('Error:', error);
            alert('An error occurred during processing. Please try again later.');
            loading.classList.add('hidden');
            form.classList.remove('hidden');
        }
    }

    // Check Status and Download
    async function checkStatus(pdfId) {
        try {
            const response = await fetch(`/check_status?pdf_id=${pdfId}`);
            if (response.ok) {
                const data = await response.json();
                if (data.status === 'completed') {
                    // Hide loading and show result
                    loading.classList.add('hidden');
                    result.classList.remove('hidden');
                    triggerConfetti();

                    // Update UI with processing details
                    document.getElementById('total-pages').textContent = data.total_pages;
                    document.getElementById('processing-time').textContent = data.processing_time !== 'N/A' ? `${data.processing_time} seconds` : 'N/A';
                

                    // Create blob from base64 data
                    const binaryData = atob(data.docx);
                    const array = new Uint8Array(binaryData.length);
                    for (let i = 0; i < binaryData.length; i++) {
                        array[i] = binaryData.charCodeAt(i);
                    }
                    const blob = new Blob([array], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
                    const url = window.URL.createObjectURL(blob);

                    // Set up download button
                    downloadButton.addEventListener('click', () => {
                        const link = document.createElement('a');
                        link.href = url;
                        link.download = 'summary.docx';
                        document.body.appendChild(link);
                        link.click();
                        document.body.removeChild(link);
                    });
                } else if (data.status === 'processing') {
                    // Update progress text
                    progressText.textContent = `Processed ${data.current_page} of ${data.total_pages} pages...`;
                    // Poll for status again
                    setTimeout(() => checkStatus(pdfId), 1000);
                } else {
                    throw new Error(data.error || 'Unknown status');
                }
            } else {
                throw new Error('Failed to check status');
            }
        } catch (error) {
            console.error('Error:', error);
            alert('An error occurred while checking the status. Please try again later.');
            loading.classList.add('hidden');
            form.classList.remove('hidden');
        }
    }

    // Particle effect
    particlesJS("particles-js", {
        particles: {
            number: { value: 80, density: { enable: true, value_area: 800 } },
            color: { value: "#ffffff" },
            shape: { type: "circle", stroke: { width: 0, color: "#000000" }, polygon: { nb_sides: 5 } },
            opacity: { value: 0.5, random: false, anim: { enable: false, speed: 1, opacity_min: 0.1, sync: false } },
            size: { value: 3, random: true, anim: { enable: false, speed: 40, size_min: 0.1, sync: false } },
            line_linked: { enable: true, distance: 150, color: "#ffffff", opacity: 0.4, width: 1 },
            move: { enable: true, speed: 6, direction: "none", random: false, straight: false, out_mode: "out", bounce: false, attract: { enable: false, rotateX: 600, rotateY: 1200 } }
        },
        interactivity: {
            detect_on: "canvas",
            events: { onhover: { enable: true, mode: "repulse" }, onclick: { enable: true, mode: "push" }, resize: true },
            modes: { grab: { distance: 400, line_linked: { opacity: 1 } }, bubble: { distance: 400, size: 40, duration: 2, opacity: 8, speed: 3 }, repulse: { distance: 200, duration: 0.4 }, push: { particles_nb: 4 }, remove: { particles_nb: 2 } }
        },
        retina_detect: true
    });

    // Typewriter effect for the title
    const titleElement = document.getElementById('title');
    new Typewriter(titleElement, {
        strings: ['PDF Summarizer'],
        autoStart: true,
        loop: true,
        delay: 75,
        deleteSpeed: 50,
    });

    // Confetti effect on successful upload
    function triggerConfetti() {
        confetti({
            particleCount: 100,
            spread: 70,
            origin: { y: 0.6 }
        });
    }

    // Add smooth scrolling to all links
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();

            document.querySelector(this.getAttribute('href')).scrollIntoView({
                behavior: 'smooth'
            });
        });
    });

    // Parallax effect removed (we're using a fixed background image)

    // Add hover effect to buttons
    const buttons = document.querySelectorAll('button, a');
    buttons.forEach(button => {
        button.addEventListener('mouseenter', () => {
            gsap.to(button, { scale: 1.05, duration: 0.3 });
        });
        button.addEventListener('mouseleave', () => {
            gsap.to(button, { scale: 1, duration: 0.3 });
        });
    });

    // Add a subtle animation to the quote section
    gsap.to('.animated-bg', {
        backgroundPosition: '100% 50%',
        duration: 10,
        repeat: -1,
        yoyo: true,
        ease: 'power1.inOut'
    });
</script>
"
now i had changed the vercel function timeout to 60 seconds..based on this change the code...make sure it can handle files more under 50mb...and the gemini model has a limit of 15 requests for minute...based on this give me the modified code



.here i am processig page by page becuase the pdf may have images and also it have handwritten text ..so thats the reason the here we are using the processing page by page as the model can process and generates the text from the images


"