i am getting error 413 in vercel when i upload the files above 4mb...and here is the documentation of the vercel about this ...and here is my app.py with document_summarisation.html code in which i am facing this error for document summarization...please help me to do something to fix it
"
How do I bypass the 4.5MB body size limit of Vercel Serverless Functions?
Learn how to deal with the body size limit of Serverless Functions on Vercel.
Last updated on June 25, 2024
Functions
Limits, Usage and Pricing
When trying to request or upload a large file from a Serverless Function, you may have seen a 413: FUNCTION_PAYLOAD_TOO_LARGE error. There are a few reasons why this error can occur, based on the following limits:

Limit

Streaming functions

Non-streaming functions

Request Body

4.5 MB

4.5 MB

Response Body

No limit

4.5 MB

To understand your function, you may first want to measure the response body size.

If the request body is too large:
This is when the body sent from the user/client to the serverless function exceeds the 4.5 MB limit.
The most common reason why this happens is when you are uploading large files. Instead, you may want to upload directly to the source.
If the response body is too large:
This is the body returned by a function to reply to the user/client. This may also appear as a 500 - FUNCTION RESPONSE PAYLOAD TOO LARGE error.
You can reduce the amount of data being returned per request.
Often, it can be hard or impossible for you to reduce the size of it depending on your needs. In this case, we recommend using streaming functions, which don't have this limit. See streaming functions for more information.
You may also want to request directly from the source.
Measure response body size
To understand how to optimize your function, you first need to know how large its response is.

Local testing
The most efficient way to measure the size of a function response body is to test locally. Perform a local build using production data or environment variables to ensure that the volume of data that the function handles closely matches production.

Once the build is complete, start the local server with pnpm start and then use curl to get the response size of a given route:


curl localhost:3000/your/page -w '%{size_download}'
You can also see this within Chrome DevTools under the Network tab. However, note that by default this will show you the gzipped size. To see the true size, enable Big Request Mode.

Reducing response size
The best way to avoid the 413 error is to reduce the amount of data being returned per function request. For example, filter for more specific data, such as 10 products instead of all products. You could also refactor large pages into smaller parallel routes, each fetching their data independently.

Streaming responses
If reducing the response size is not feasible, consider streaming your function responses. For customers using Next.js, refer to the official Next.js documentation for further guidance on streaming.

Request directly from the source
Serverless Functions are designed to respond quickly to clients and should be treated like a lightweight API layer, not a media server.

If you have a large file like a video that you need to send to a client, you should consider storing those assets in a dedicated media host and making them retrievable with a pre-signed URL that contains access control policies directly in the URL.

This will ensure that your assets are securely accessed in a manner that you control. Cloudinary, FaunaDB, and AWS S3 are all examples of services that support this.

Upload directly to the source
Similar to requesting assets securely, you can upload large files directly to a media host from your browser without needing a Serverless Function as a proxy. Vercel Blob, Cloudinary, FaunaDB, and AWS S3 are all solutions for this among others.

If you need to upload files larger than 4.5 MB to Vercel Blob, you can use client uploads, where the file is sent directly from the client (e.g. a browser) to Vercel Blob. This transfer is done securely so as not to expose your Vercel Blob store to anonymous uploads. The security mechanism is based on a token exchange between your server and Vercel Blob. Use the following steps to achieve this:

Step 1: Create a client upload page
This page allows you to upload files to Vercel Blob. The files will go directly from the browser to Vercel Blob without going through your server. Behind the scenes, the upload is done securely by exchanging a token with your server before uploading the file.

app/avatar/upload/page.tsx

'use client';
 
import { type PutBlobResult } from '@vercel/blob';
import { upload } from '@vercel/blob/client';
import { useState, useRef } from 'react';
 
export default function AvatarUploadPage() {
  const inputFileRef = useRef<HTMLInputElement>(null);
  const [blob, setBlob] = useState<PutBlobResult | null>(null);
  return (
    <>
      <h1>Upload Your Avatar</h1>
 
      <form
        onSubmit={async (event) => {
          event.preventDefault();
 
          const file = inputFileRef.current.files[0];
 
          const newBlob = await upload(file.name, file, {
            access: 'public',
            handleUploadUrl: '/api/avatar/upload',
          });
 
          setBlob(newBlob);
        }}
      >
        <input name="file" ref={inputFileRef} type="file" required />
        <button type="submit">Upload</button>
      </form>
      {blob && (
        <div>
          Blob url: <a href={blob.url}>{blob.url}</a>
        </div>
      )}
    </>
  );
}
Step 2: Create a client upload route
The responsibility of this client upload route is to:

Generate tokens for client uploads
Listen for completed client uploads, so you can update your database with the URL of the uploaded file for example
The @vercel/blob npm package exposes a helper to implement said responsibilities.

app/api/avatar/upload/route.ts

import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
 
export async function POST(request: Request): Promise<NextResponse> {
  const body = (await request.json()) as HandleUploadBody;
 
  try {
    const jsonResponse = await handleUpload({
      body,
      request,
      onBeforeGenerateToken: async (
        pathname: string,
        /* clientPayload?: string, */
      ) => {
        // Generate a client token for the browser to upload the file
 
        // ⚠️ Authenticate users before generating the token.
        // Otherwise, you're allowing anonymous uploads.
        const { user } = await auth(request);
        const userCanUpload = canUpload(user, pathname);
        if (!userCanUpload) {
          throw new Error('Not authorized');
        }
 
        return {
          allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'],
          tokenPayload: JSON.stringify({
            // optional, sent to your server on upload completion
            userId: user.id,
          }),
        };
      },
      onUploadCompleted: async ({ blob, tokenPayload }) => {
        // Get notified of client upload completion
        // ⚠️ This will not work on `localhost` websites,
        // Use ngrok or similar to get the full upload flow
 
        console.log('blob upload completed', blob, tokenPayload);
 
        try {
          // Run any logic after the file upload completed
          // const { userId } = JSON.parse(tokenPayload);
          // await db.update({ avatar: blob.url, userId });
        } catch (error) {
          throw new Error('Could not update user');
        }
      },
    });
 
    return NextResponse.json(jsonResponse);
  } catch (error) {
    return NextResponse.json(
      { error: (error as Error).message },
      { status: 400 }, // The webhook will retry 5 times waiting for a 200
    );
  }
}
When your local website is served on http://localhost:3000, then the onUploadCompleted step won't succeed as Vercel Blob cannot contact your localhost. Instead, we recommend you run your local application through a tunneling service like ngrok , so you can experience the full Vercel Blob development flow locally.
"
"
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
from functools import wraps
import tenacity
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
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 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()


app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024  # 10MB
# 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-8b',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



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


#docuement summarize 

import time
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
import google.api_core.exceptions
import threading

# Token bucket for rate limiting
# Rate limiting parameters
REQUEST_LIMIT = 15
TIME_WINDOW = 60

class TokenBucket:
    def __init__(self, tokens, fill_rate):
        self.capacity = tokens
        self.tokens = tokens
        self.fill_rate = fill_rate
        self.last_check = time.time()
        self.lock = threading.Lock()

    def get_token(self):
        with self.lock:
            now = time.time()
            time_passed = now - self.last_check
            self.tokens = min(self.capacity, self.tokens + time_passed * self.fill_rate)
            self.last_check = now
            if self.tokens >= 1:
                self.tokens -= 1
                return True
            return False

# Initialize the token bucket (15 tokens, refill 1 token every 4 seconds)
token_bucket = TokenBucket(REQUEST_LIMIT, 1 / (TIME_WINDOW / REQUEST_LIMIT))

def rate_limit_check():
    while not token_bucket.get_token():
        time.sleep(1)
# Rate limiting parameters

rate_limit_lock = None  # Replace with your lock mechanism
last_reset_time = time.time()
request_count = 0

def rate_limited(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        rate_limit_check()
        return func(*args, **kwargs)
    return wrapper


import fitz  # PyMuPDF
import io
from PIL import Image
import base64

def process_page(pdf_document, page_num, doc_ref):
    logger.info(f"Processing page {page_num + 1}")
    page = pdf_document[page_num]
    
    # Convert page to image
    pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))  # Increase resolution
    img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
    
    # Convert image to base64
    buffered = io.BytesIO()
    img.save(buffered, format="PNG")
    img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')

    try:
        page_summary = generate_summary(img_base64)
        if page_summary:
            logger.info(f"Summary generated for page {page_num + 1}")
            doc_ref.update({
                'current_page': page_num + 1,
                'summary': firestore.ArrayUnion([page_summary])
            })
        else:
            logger.warning(f"Failed to generate summary for page {page_num + 1}")
            doc_ref.update({
                'current_page': page_num + 1,
                'summary': firestore.ArrayUnion([f"(Summary not available for page {page_num + 1})"])
            })
    except Exception as e:
        logger.error(f"Error processing page {page_num + 1}: {e}")
        doc_ref.update({
            'current_page': page_num + 1,
            'summary': firestore.ArrayUnion([f"(Error processing page {page_num + 1})"])
        })

@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})

@rate_limited
def generate_summary(image_base64):
    rate_limit_check()  # Wait for a token before making the API call
    
    prompt = [
        """Analyze the following image, which is a page from a document, and provide 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.

- 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 image to analyze and summarize:
""",
        Image.open(io.BytesIO(base64.b64decode(image_base64)))
    ]

    try:
        response = model_vision.generate_content(prompt, safety_settings=safety_settings)
        summary_text = response.text
        logger.info("Summary generated successfully")
        return summary_text
    except google.api_core.exceptions.ResourceExhausted as e:
        logger.warning(f"Resource exhausted: {e}. Retrying...")
        raise  # This will trigger a retry
    except Exception as e:
        logger.error(f"Error in Gemini API call: {e}")
        return None  # Return None for non-retryable errors

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.lower().endswith('.pdf'):
        try:
            # Read the file into memory
            file_content = file.read()
            file_size = len(file_content)

            # Check file size (10MB limit)
            if file_size > 10 * 1024 * 1024:
                return jsonify({'error': 'File size exceeds 10MB limit'}), 400

            # 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_string(file_content, content_type='application/pdf')

            # Get the total number of pages
            pdf_document = fitz.open(stream=file_content, 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,
                'file_size': file_size
            })

            return jsonify({
                'pdf_id': pdf_id,
                'total_pages': total_pages,
                'file_size': file_size
            }), 200
        except Exception as e:
            logging.error(f"Error uploading file: {e}")
            return jsonify({'error': f'Error uploading file: {str(e)}'}), 500
    else:
        return jsonify({'error': 'Invalid file type. Please upload a PDF.'}), 400

# Update the process_pdf_endpoint function
@app.route('/process_pdf', methods=['POST'])
def process_pdf_endpoint():
    data = request.get_json()
    pdf_id = data.get('pdf_id')
    if not pdf_id:
        logger.error("No PDF ID provided")
        return jsonify({'error': 'No PDF ID provided.'}), 400

    doc_ref = db.collection('pdf_processes').document(pdf_id)
    doc = doc_ref.get()
    if not doc.exists:
        logger.error(f"Invalid PDF ID: {pdf_id}")
        return jsonify({'error': 'Invalid PDF ID.'}), 400
    
    result = doc.to_dict()
    current_page = result['current_page']
    total_pages = result['total_pages']

    if current_page >= total_pages:
        logger.info(f"PDF {pdf_id} processing already completed")
        return jsonify({'status': 'completed'}), 200

    try:
        logger.info(f"Processing PDF {pdf_id}, page {current_page + 1} of {total_pages}")
        blob = bucket.blob(f'pdfs/{pdf_id}.pdf')
        pdf_bytes = blob.download_as_bytes()
        pdf_document = fitz.open(stream=pdf_bytes, filetype="pdf")

        process_page(pdf_document, current_page, doc_ref)
        pdf_document.close()

        updated_doc = doc_ref.get().to_dict()
        if updated_doc['current_page'] >= total_pages:
            logger.info(f"PDF {pdf_id} processing completed")
            doc_ref.update({
                'status': 'completed',
                'processing_end_time': time.time()
            })
            blob.delete()
            return jsonify({'status': 'completed'}), 200
        else:
            logger.info(f"PDF {pdf_id} processing in progress. Current page: {updated_doc['current_page']}")
            return jsonify({
                'status': 'processing',
                'current_page': updated_doc['current_page'],
                'total_pages': total_pages
            }), 200

    except Exception as e:
        logger.error(f"Error processing PDF {pdf_id}: {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:
        logger.error("No PDF ID provided for status check")
        return jsonify({'error': 'No PDF ID provided.'}), 400

    doc_ref = db.collection('pdf_processes').document(pdf_id)
    doc = doc_ref.get()
    if not doc.exists:
        logger.error(f"Invalid PDF ID for status check: {pdf_id}")
        return jsonify({'error': 'Invalid PDF ID.'}), 400
    
    result = doc.to_dict()
    status = result.get('status', 'processing')

    if status == 'completed':
        logger.info(f"Status check: PDF {pdf_id} processing completed")
        summary = '\n\n'.join(result['summary'])
        docx_buffer = create_word_document(summary)
        docx_base64 = base64.b64encode(docx_buffer.getvalue()).decode('utf-8')

        processing_time = 'N/A'
        if result.get('processing_start_time') and result.get('processing_end_time'):
            processing_time = int(result['processing_end_time'] - result['processing_start_time'])

        return jsonify({
            'status': 'completed',
            'docx': docx_base64,
            'total_pages': result.get('total_pages', 0),
            'processing_time': processing_time
        }), 200
    else:
        logger.info(f"Status check: PDF {pdf_id} processing in progress. Current page: {result.get('current_page', 0)}")
        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)
"
"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SummarAI Nexus</title>
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;700&family=Roboto:wght@300;400;500;700&family=Dancing+Script:wght@600&family=Kaushan+Script&display=swap" rel="stylesheet" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet" />
    <link href="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js" defer></script>
    <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
    <script src="https://unpkg.com/typewriter-effect@latest/dist/core.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
    <style>
        body {
            font-family: 'Montserrat', sans-serif;
            background-image: url('https://images.stockcake.com/public/4/e/f/4ef87898-2e97-4e79-8da4-9a82a2288fed/enchanted-nighttime-reading-stockcake.jpg'); 
            background-size: cover;
            background-repeat: no-repeat;
            background-attachment: fixed;
        }
        .glass-effect {
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.2);
            box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
        }
        .animate-fade-in {
            opacity: 0;
            animation: fadeIn 0.5s ease-in forwards;
        }
        @keyframes fadeIn {
            to {
                opacity: 1;
            }
        }
        .loader {
            border-top-color: #3498db;
            animation: spinner 1s linear infinite;
        }
        @keyframes spinner {
            to {
                transform: rotate(360deg);
            }
        }
        .animated-bg {
            background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
            background-size: 400% 400%;
            animation: gradientBG 15s ease infinite;
        }
        @keyframes gradientBG {
            0% {
                background-position: 0% 50%;
            }
            50% {
                background-position: 100% 50%;
            }
            100% {
                background-position: 0% 50%;
            }
        }
        .hover-effect {
            transition: all 0.3s ease;
        }
        .hover-effect:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        }
        @media (max-width: 768px) {
            .flex-container {
                flex-direction: column;
            }
            .quote-container {
                width: 100%;
                margin-bottom: 2rem;
            }
            .main-content {
                width: 100%;
                padding-left: 0;
            }
            #title {
                font-size: 3rem;
            }
            .file-upload-label {
                height: 12rem;
            }
        }
    </style>
</head>
<body class="min-h-screen flex items-center justify-center p-4"> 
    <div id="particles-js" class="absolute inset-0"></div>
    <div class="bg-white bg-opacity-50 rounded-2xl shadow-2xl w-full max-w-6xl p-4 sm:p-8 animate-fade-in flex flex-container glass-effect relative z-10">
        <!-- Quote of the Day -->
        <div class="quote-container w-full sm:w-1/3 p-6 rounded-2xl text-white flex flex-col items-center justify-center animated-bg mb-4 sm:mb-0" data-aos="fade-right">
            <h2 class="text-3xl sm:text-4xl font-bold mb-4" style="font-family: 'Kaushan Script', cursive;">Quote of the Day</h2>
            <p id="quote" class="text-xl sm:text-2xl text-center"></p>
        </div>
        <!-- Main Content -->
        <div class="main-content w-full sm:w-2/3 sm:pl-8">
            <!-- Header -->
            <div class="text-center mb-8" data-aos="zoom-in">
                <h1 id="title" class="text-4xl sm:text-6xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-teal-400">
                    PDF Summarizer
                </h1>
                <p class="text-green-700 mt-4 text-lg">
                    Quickly generate summaries of your PDF documents
                </p>
            </div>

            <!-- 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 5MB)
                            </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>
</body>
</html>
"