<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>News Summarizer</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.3/cdn.min.js"
      defer
    ></script>
  </head>
  <body
    class="bg-gray-100 min-h-screen flex items-center justify-center"
    x-data="{ newsData: [], loading: false }"
  >
    <div class="container mx-auto px-4 py-8">
      <h1 class="text-4xl font-bold text-center mb-8 text-blue-600">
        News Summarizer
      </h1>
      <form @submit.prevent="submitSearch" class="mb-8 flex justify-center">
        <input
          type="text"
          name="query"
          placeholder="Enter search term"
          class="px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
        />
        <button
          type="submit"
          class="bg-blue-500 text-white px-6 py-2 rounded-r-lg hover:bg-blue-600 transition duration-300"
        >
          Search
        </button>
      </form>

      <!-- Loading Spinner -->
      <div x-show="loading && newsData.length === 0" class="text-center">
        <div
          class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-blue-500 mx-auto"
        ></div>
      </div>

      <!-- Articles -->
      <div class="space-y-6">
        <template x-for="article in newsData" :key="article[0]">
          <div class="bg-white rounded-lg shadow-lg p-6">
            <h2 x-text="article[1]" class="text-2xl font-semibold mb-4"></h2>
            <img
              :src="article[2]"
              :alt="article[1]"
              class="w-full h-64 object-cover rounded-lg mb-4"
            />
            <p x-text="article[3]" class="text-gray-700 mb-4"></p>
            <a
              :href="article[0]"
              target="_blank"
              class="text-blue-500 hover:underline"
              >Read full article</a
            >
          </div>
        </template>
      </div>
    </div>

    <script>
      function submitSearch() {
        const query = document.querySelector('input[name="query"]').value;
        this.loading = true;
        this.newsData = []; // Reset newsData
        const indices = [0, 1, 2];
        const vm = this; // Reference to Alpine.js component
        let articlesLoaded = 0;

        indices.forEach((index) => {
          fetch("/search", {
            method: "POST",
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
            body: `query=${encodeURIComponent(query)}&index=${index}`,
          })
            .then((response) => response.json())
            .then((data) => {
              articlesLoaded++;
              if (!data.error) {
                vm.newsData.push(data);
              }
              // Hide loading spinner after first article is loaded
              if (vm.loading && vm.newsData.length > 0) {
                vm.loading = false;
              }
            })
            .catch((error) => {
              console.error("Error:", error);
              articlesLoaded++;
              if (articlesLoaded === indices.length) {
                vm.loading = false;
              }
            });
        });
      }
    </script>
  </body>
</html>
import os
import aiohttp
import asyncio
import dotenv
import google.generativeai as genai
from quart import Quart, render_template, request, jsonify

app = Quart(__name__)

with open("sysprompt.txt", "r") as f:
    sysprompt = f.read()

dotenv.load_dotenv()
NEWS_API_KEY = os.environ.get("NEWS_API_KEY")
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")

genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel(
    model_name="gemini-1.5-flash", system_instruction=sysprompt
)


async def generate_summary(text):
    summary = await model.generate_content_async(text)
    return summary


async def get_news(query):
    async with aiohttp.ClientSession() as session:
        try:
            async with session.get(
                f"https://newsapi.org/v2/everything?q={query}&apiKey={NEWS_API_KEY}"
            ) as response:
                response.raise_for_status()
                response_data = await response.json()
                return response_data
        except aiohttp.ClientResponseError as e:
            print(f"Error fetching for {query}: {e}")
            return None


def extract_links(news_data):
    return [article.get("url", "no link found") for article in news_data["articles"]]


def extract_headlines(news_data):
    return [article.get("title", "no title found") for article in news_data["articles"]]


def extract_image_urls(news_data):
    return [
        article.get("urlToImage", "no image found") for article in news_data["articles"]
    ]


async def scrape_link(url):
    jina_url = "https://r.jina.ai/" + url
    async with aiohttp.ClientSession() as session:
        try:
            async with session.get(jina_url) as response:
                response.raise_for_status()
                return await response.text()
        except aiohttp.ClientResponseError as e:
            print(f"Error fetching for {url}: {e}")
            return None


async def generate_single_news_data(news_data, news_data_index):
    link, headline, image_url = news_data[news_data_index]
    scrape = await scrape_link(link)
    summary = await generate_summary(scrape)
    return [link, headline, image_url, summary.text]


async def process_search(query, index):
    news = await get_news(query)
    if news is None or "articles" not in news or not news["articles"]:
        return None
    links = extract_links(news)
    headlines = extract_headlines(news)
    image_urls = extract_image_urls(news)
    news_data = list(zip(links, headlines, image_urls))
    if index < len(news_data):
        new_data = await generate_single_news_data(news_data, index)
        return new_data
    else:
        return None


@app.route("/")
async def index():
    return await render_template("index.html")


@app.route("/search", methods=["POST"])
async def search():
    form = await request.form
    query = form.get("query")
    index = int(form.get("index", 0))
    new_data = await process_search(query, index)
    if new_data:
        return jsonify(new_data)
    else:
        return jsonify({"error": "Article not found"}), 404
what could i add to this news website ?