Building an AI App with a Personalized News Summarizer

Building an AI App with a Personalized News Summarizer

Looking for a hands-on project to sharpen your Python and AI skills? The best way to learn is by building, and in this tutorial, we’ll create a practical AI application from scratch. We’ll connect to live APIs, build a simple web server, and use a Large Language Model to create a personalized news summarizer.

In this project-based tutorial, we’re going to do exactly that. We’ll build a simple but powerful web application that fetches the latest news on any topic you choose and uses the power of AI to give you a quick, easy-to-read summary.

This is a perfect project for your portfolio. It demonstrates real-world skills in Python, web development, and one of the hottest topics in tech: Retrieval-Augmented Generation (RAG). Let’s get started!

What We’re Building

Here’s the game plan:

  1. A user types a topic (like “electric cars” or “space exploration”) into a web page.
  2. Our Python backend springs into action, fetching the latest news articles on that topic from a live News API.
  3. For each article, we’ll send its content to a Large Language Model (LLM)—in this case, Google’s Gemini—and ask it to generate a concise summary.
  4. The summaries are then sent back to the web page for the user to read.

The Tech Stack (What You’ll Learn)

  • Python: The core language for our backend logic.
  • Flask: A beginner-friendly Python framework for building the web server.
  • APIs: We’ll learn how to request data from two different APIs: NewsAPI (for articles) and the Google AI API (for summarization).
  • HTML & JavaScript: For the simple, functional frontend.
  • Virtual Environments: The professional way to manage project dependencies.

Phase 1: Setting Up Your Digital Workspace

First things first, let’s get our project folder and tools ready.

  1. Create a Project Folder: On your Desktop, create a new folder called NewsSummarizer.
  2. Open Your Terminal: We’ll be using the command line to set things up.
  3. Create a Virtual Environment: This is a crucial best practice. It creates an isolated “sandbox” for our project’s libraries. Navigate into your project folder and run the following commands:# Navigate into your new folder cd ~/Desktop/NewsSummarizer # Create the virtual environment python3 -m venv venv # Activate it (you'll need to do this every time you work on the project) source venv/bin/activate
    You’ll know it’s active when you see (venv) at the start of your terminal prompt.
  4. Install the Libraries: With your environment active, run this single command to install all the Python libraries we need:pip install Flask requests google-generativeai python-dotenv

Phase 2: Getting Your API Keys

API keys are like secret passwords that let our application access services. We need two free ones.

  • NewsAPI Key: Go to newsapi.org, click “Get API Key,” and sign up. You’ll find the key on your dashboard.
  • Google AI (Gemini) Key: Go to aistudio.google.com. Click “Get API key” and then “Create API key in new project.”

Once you have both keys, create a file in your NewsSummarizer folder named .env. The leading dot is important! Inside this file, paste your keys like so:

NEWS_API_KEY=YOUR_NEWS_API_KEY_HERE
GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY_HERE

Phase 3: Building the Brains (The Python Backend)

This is where the main logic lives. Create a file named app.py in your project folder and add the following code. It’s fully commented to explain every step.

# --- Import Necessary Libraries ---

import os  # Used to access environment variables (our API keys)
import requests  # Used to make HTTP requests to the News API
import google.generativeai as genai # The Google AI library for Gemini
from flask import Flask, request, jsonify, render_template # The main components from Flask for building the web app
from dotenv import load_dotenv # Used to load the environment variables from our .env file

# --- Initial Setup ---

# Load the environment variables from the .env file
load_dotenv()

# Create a Flask application instance
# The 'static_folder' and 'template_folder' tell Flask where to find our CSS/JS and HTML files
app = Flask(__name__, static_folder='static', template_folder='.')

# --- API Configuration ---

# Configure the Google AI client with the API key from our .env file
# This line sets up the connection to the Gemini model
try:
    genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
    # Initialize the generative model we want to use
    # *** FIX: Updated model name to a more current and available version ***
    model = genai.GenerativeModel('gemini-1.5-flash-latest')
except Exception as e:
    print(f"Error configuring Google AI: {e}")
    # You might want to handle this more gracefully in a real app
    model = None 

# Get the News API key from our .env file
NEWS_API_KEY = os.environ.get("NEWS_API_KEY")
# The base URL for the News API's 'everything' endpoint
NEWS_API_URL = "https://newsapi.org/v2/everything"


# --- Main Application Routes ---

# This defines the main (or "root") page of our web app
@app.route('/')
def index():
    """
    This function runs when a user visits the main URL (e.g., http://127.0.0.1:5000)
    It tells Flask to find the 'index.html' file and send it to the user's browser.
    """
    return render_template('index.html')

# This defines the '/summarize' endpoint that our frontend will call
@app.route('/summarize', methods=['POST'])
def summarize():
    """
    This function handles the main logic: fetching and summarizing news.
    It's triggered when the frontend sends a POST request to '/summarize'.
    """
    # Check if the API keys are available
    if not NEWS_API_KEY or not model:
        return jsonify({'error': 'API key configuration is missing. Please check your .env file.'}), 500

    # --- 1. Get Keywords from Frontend ---
    # Get the JSON data sent from the frontend JavaScript
    data = request.get_json()
    # Extract the 'keywords' from the data
    keywords = data.get('keywords')

    # If no keywords were provided, return an error
    if not keywords:
        return jsonify({'error': 'No keywords provided.'}), 400

    # --- 2. Retrieve Articles (The "R" in RAG) ---
    try:
        # Set up the parameters for our News API request
        params = {
            'q': keywords,       # The search keywords
            'apiKey': NEWS_API_KEY, # Your API key
            'language': 'en',      # We want English articles
            'pageSize': 5,         # Let's get the top 5 articles
            'sortBy': 'relevancy'  # Sort by the most relevant first
        }
        # Make the request to the News API
        response = requests.get(NEWS_API_URL, params=params)
        # Raise an exception if the request failed (e.g., 404, 500)
        response.raise_for_status()
        # Parse the JSON response from the API
        news_data = response.json()
        articles = news_data.get('articles', [])

        # If no articles were found, return a specific message
        if not articles:
            return jsonify({'error': f"No articles found for '{keywords}'. Try a different topic."}), 404

    except requests.exceptions.RequestException as e:
        # Handle network errors or bad responses from the News API
        return jsonify({'error': f'Could not fetch news: {e}'}), 500

    # --- 3. Generate Summaries (The "G" in RAG) ---
    summaries = []
    for article in articles:
        # We only care about articles with some content
        if not article.get('content'):
            continue

        # Create a detailed prompt for the AI
        # This is where we "augment" the AI's knowledge with the retrieved article
        prompt = f"""
        Please provide a concise, one-paragraph summary of the following news article.
        Focus on the key takeaways and the most important information.

        ARTICLE TITLE: {article['title']}
        ARTICLE CONTENT: {article['content']}

        SUMMARY:
        """

        try:
            # Send the prompt to the Gemini model
            ai_response = model.generate_content(prompt)
            # Extract the text from the AI's response
            summary_text = ai_response.text
        except Exception as e:
            # If the AI fails for one article, we can just skip it
            print(f"Could not generate summary for '{article['title']}': {e}")
            summary_text = "Summary could not be generated for this article."

        # Add the processed article to our list of summaries
        summaries.append({
            'title': article['title'],
            'source': article['source']['name'],
            'summary': summary_text,
            'url': article['url']
        })

    # --- 4. Send Results to Frontend ---
    # Return the list of summaries as a JSON response
    return jsonify({'summaries': summaries})


# --- Run the Application ---

# This is a standard Python practice to make sure the server only runs
# when the script is executed directly (not when imported as a module)
if __name__ == '__main__':
    # Starts the Flask development server
    # debug=True allows you to see errors in the browser and automatically reloads the server when you save changes
    app.run(debug=True)

Phase 4: Creating the Face (The Frontend)

Now for the user interface. Create a file named index.html and add this code. It’s simple HTML with some JavaScript to talk to our Python backend.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple News Summarizer</title>
    <style>
        /* A little bit of basic styling to make it readable */
        body { 
            font-family: sans-serif; 
            max-width: 800px; 
            margin: 40px auto; 
            padding: 0 20px;
            background-color: #f9f9f9;
        }
        input { 
            width: 70%; 
            padding: 8px; 
            font-size: 16px; 
        }
        button { 
            padding: 8px 12px; 
            font-size: 16px; 
            cursor: pointer; 
        }
        #results { 
            margin-top: 20px; 
        }
        .article { 
            background-color: #fff;
            border: 1px solid #ddd; 
            padding: 15px; 
            margin-bottom: 15px; 
            border-radius: 5px;
        }
        h2 { 
            margin-top: 0; 
        }
    </style>
</head>
<body>

    <h1>Personalized News Summarizer</h1>
    <p>Enter a topic to get AI-powered summaries of the latest news.</p>

    <!-- Input section -->
    <input type="text" id="keywords" placeholder="e.g., 'electric vehicles'">
    <button id="summarize-btn">Get Summaries</button>

    <!-- Loading/Error message area -->
    <p id="status"></p>

    <!-- Results will be displayed here -->
    <div id="results"></div>

    <script>
        // --- Get references to the HTML elements we need ---
        const keywordsInput = document.getElementById('keywords');
        const summarizeBtn = document.getElementById('summarize-btn');
        const resultsDiv = document.getElementById('results');
        const statusP = document.getElementById('status');

        // --- Listen for a click on the button ---
        summarizeBtn.addEventListener('click', getSummaries);

        // --- Listen for the "Enter" key in the input box ---
        keywordsInput.addEventListener('keypress', function(event) {
            if (event.key === 'Enter') {
                getSummaries();
            }
        });

        // --- Main function to fetch and display summaries ---
        async function getSummaries() {
            // 1. Get the user's keywords from the input box
            const keywords = keywordsInput.value;

            // Simple validation
            if (!keywords) {
                statusP.textContent = "Please enter a topic.";
                return;
            }

            // 2. Clear previous results and show a loading message
            resultsDiv.innerHTML = '';
            statusP.textContent = 'Fetching and summarizing news...';

            try {
                // 3. Send the keywords to our Python backend
                // This is the core of the frontend-backend communication.
                const response = await fetch('/summarize', {
                    method: 'POST', // We are sending data, so we use POST
                    headers: {
                        'Content-Type': 'application/json' // Tell the server we're sending JSON
                    },
                    body: JSON.stringify({ keywords: keywords }) // Convert our JS object to a JSON string
                });

                // 4. Get the response from the backend
                const data = await response.json();

                // 5. Check if the backend sent an error
                if (data.error) {
                    statusP.textContent = `Error: ${data.error}`;
                } else {
                    // If successful, display the summaries
                    statusP.textContent = ''; // Clear the status message
                    displaySummaries(data.summaries);
                }

            } catch (error) {
                // This catches network errors (e.g., if the server is down)
                statusP.textContent = 'An error occurred. Make sure the Python server is running.';
                console.error("Fetch error:", error);
            }
        }

        // --- Helper function to display the articles ---
        function displaySummaries(summaries) {
            // If the backend found no articles
            if (summaries.length === 0) {
                statusP.textContent = "No articles found for that topic.";
                return;
            }

            // Loop through each summary and create HTML for it
            summaries.forEach(article => {
                // Create a new div for the article
                const articleDiv = document.createElement('div');
                articleDiv.className = 'article'; // Add a class for styling

                // Use innerHTML to create the content for the article card
                articleDiv.innerHTML = `
                    <h2>${article.title}</h2>
                    <p><strong>Source:</strong> ${article.source}</p>
                    <p>${article.summary}</p>
                    <a href="${article.url}" target="_blank">Read Full Article</a>
                `;

                // Add the new article card to the results area
                resultsDiv.appendChild(articleDiv);
            });
        }
    </script>

</body>
</html>

Phase 5: Bring It to Life!

You’re all set! Go to your terminal (make sure you’re in the NewsSummarizer folder and your virtual environment is active) and run the server:

python3 app.py

Open your web browser and go to http://127.0.0.1:5000. Your app should be running!

Phase 6: Share Your Work on GitHub

Now it’s time to show off your project. Create a new, empty repository on GitHub. Then, you can upload your files manually.

IMPORTANT: When you upload, only add app.py and index.html. NEVER make your .env file or your venv folder public.

Final Thoughts

Congratulations! You’ve just built a full-stack web application that uses a powerful AI model. This project covers so many essential skills for modern developers.

From here, you could try adding new features:

  • Improve the UI with a framework like Tailwind CSS.
  • Let the user select the number of articles to summarize.
  • Add a “Copy Summary” button.

Feel free to grab the code from my GitHub repository here to check your work: [Link to my GitHub repository]

Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *