News_Summarizer / app.py
MathWizard1729's picture
Update app.py
27ad88b verified
raw
history blame
10.2 kB
import streamlit as st
import requests
import boto3
import os
from dotenv import load_dotenv
import json
# ───────────────────────────────────────────────────────────────────────────────
# Load environment variables (make sure your .env has NEWS_API_KEY, SERPER_API_KEY, AWS_REGION, etc.)
load_dotenv()
NEWS_API_KEY = os.getenv("NEWS_API_KEY")
SERPER_API_KEY = os.getenv("SERPER_API_KEY")
AWS_REGION = os.getenv("AWS_REGION")
# Setup AWS Bedrock client
bedrock = boto3.client("bedrock-runtime", region_name=AWS_REGION)
# ───────────────────────────────────────────────────────────────────────────────
# Page configuration
st.set_page_config(
page_title="πŸ“° News Summarizer Agent",
page_icon="πŸ“°",
layout="wide", # wide layout for more room
initial_sidebar_state="expanded"
)
# ───────────────────────────────────────────────────────────────────────────────
# Custom CSS for vibrant UI
st.markdown(
"""
<style>
/* Change background color of the main area */
.stApp {
background-color: #F7F9FC;
}
/* Style for the title */
.app-title {
font-size: 2.5rem;
color: #1E3A8A;
font-weight: 700;
margin-bottom: 0.2rem;
}
/* Style for the subtitle/description */
.app-subtitle {
font-size: 1.1rem;
color: #374151;
margin-bottom: 1.5rem;
}
/* Card container styling */
.news-card {
background-color: #FFFFFF;
border-radius: 10px;
padding: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.05);
}
/* Header style inside each card */
.news-header {
background-color: #E0F2FE;
border-radius: 8px;
padding: 0.5rem 1rem;
margin-bottom: 0.8rem;
}
.news-header h4 {
margin: 0;
color: #0369A1;
}
/* Article title style */
.article-title {
font-size: 1.2rem;
font-weight: 600;
color: #1F2937;
margin-bottom: 0.5rem;
}
/* Summary text style */
.article-summary {
font-size: 1rem;
color: #4B5563;
margin-bottom: 0.7rem;
}
/* β€œRead more” link style */
.read-link {
font-size: 0.95rem;
color: #1D4ED8;
text-decoration: none;
font-weight: 500;
}
.read-link:hover {
text-decoration: underline;
}
/* Spinner/loading text */
.stSpinner > div {
background-color: #FFFFFF !important;
}
</style>
""",
unsafe_allow_html=True
)
# ───────────────────────────────────────────────────────────────────────────────
# Sidebar (for API info, instructions, etc.)
with st.sidebar:
st.markdown("## ℹ️ About This App")
st.markdown(
"""
- Enter any **topic**, **company name**, or **keywords** above and click **Get News**.
- This app will fetch up to 3 articles from **NewsAPI** and 2 from **Serper**, then summarize them using AWS Bedrock (Claude).
- You need valid `NEWS_API_KEY`, `SERPER_API_KEY`, and AWS credentials / region configured.
"""
)
st.markdown("---")
st.markdown("## πŸ”‘ API Keys")
st.markdown(
"""
- **NEWS_API_KEY**: Used to fetch articles from NewsAPI.org
- **SERPER_API_KEY**: Used to fetch Google‐News‐style results via Serper.dev
- **AWS Credentials**: For invoking Claude on Bedrock
"""
)
st.markdown("---")
st.markdown("## πŸ“š Resources")
st.markdown(
"""
- [NewsAPI Documentation](https://newsapi.org/docs)
- [Serper.dev Docs](https://serper.dev/docs)
- [AWS Bedrock Claude Guide](https://docs.aws.amazon.com/bedrock/latest/developerguide/claude.html)
"""
)
# ───────────────────────────────────────────────────────────────────────────────
# Main Title / Header
st.markdown('<div class="app-title">πŸ“° News Summarizer Agent</div>', unsafe_allow_html=True)
st.markdown(
'<div class="app-subtitle">Get the top 5 latest news with concise summaries on any topic or company you choose.</div>',
unsafe_allow_html=True
)
# Input box and button
query = st.text_input(
label="",
placeholder="e.g. Artificial Intelligence, Tesla, Global Markets...",
key="search_query"
)
btn = st.button("Get News", use_container_width=True)
# ───────────────────────────────────────────────────────────────────────────────
# Functions to fetch articles and summarize
def get_newsapi_articles(q: str):
"""
Fetch up to 3 articles from NewsAPI based on query,
sorted by published date (newest first).
"""
url = "https://newsapi.org/v2/everything"
params = {
"q": q,
"sortBy": "publishedAt",
"pageSize": 3,
"apiKey": NEWS_API_KEY
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
return data.get("articles", [])
except Exception as e:
st.error(f"Error fetching from NewsAPI: {e}")
return []
def get_serper_articles(q: str):
"""
Fetch up to 2 news snippets from Serper (Google News API wrapper).
"""
url = "https://google.serper.dev/news"
headers = {"X-API-KEY": SERPER_API_KEY}
data = {"q": q}
try:
response = requests.post(url, headers=headers, json=data, timeout=10)
response.raise_for_status()
data = response.json()
return data.get("news", [])[:2]
except Exception as e:
st.error(f"Error fetching from Serper: {e}")
return []
def summarize_with_bedrock(title: str, content: str) -> str:
"""
Send the article title + content to AWS Bedrock (Claude) and get a 3–5 line summary.
"""
prompt = (
f"Summarize the following news article in 3–5 lines:\n\n"
f"Title: {title}\n\nContent: {content}\n\nSummary:"
)
body = {
"anthropic_version": "bedrock-2023-05-31",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 300,
"temperature": 0.7
}
try:
response = bedrock.invoke_model(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
body=json.dumps(body),
contentType="application/json",
)
result = json.loads(response["body"].read())
# The response format may vary; adjust indexing if necessary
return result["content"][0]["text"].strip()
except Exception as e:
return f"⚠️ Failed to summarize: {e}"
# ───────────────────────────────────────────────────────────────────────────────
# When button is clicked
if btn and query:
st.info(f"πŸ” Searching for news on <b>{query}</b>...", unsafe_allow_html=True)
# Fetch articles concurrently / sequentially
newsapi_articles = get_newsapi_articles(query)
serper_articles = get_serper_articles(query)
# Collect and normalize
all_articles = []
# From NewsAPI
for art in newsapi_articles:
title = art.get("title") or "No Title"
content = art.get("description") or art.get("content") or ""
url = art.get("url") or ""
if content:
all_articles.append({"title": title, "content": content, "url": url})
# From Serper
for art in serper_articles:
title = art.get("title") or "No Title"
content = art.get("snippet") or ""
url = art.get("link") or ""
if content:
all_articles.append({"title": title, "content": content, "url": url})
# If no articles found
if not all_articles:
st.warning("No articles found for that query. Try something else?")
else:
# Loop through each article and display as a β€œcard”
for i, article in enumerate(all_articles, start=1):
with st.spinner(f"πŸ–‹οΈ Summarizing Article {i}..."):
summary = summarize_with_bedrock(article["title"], article["content"])
# Build a β€œcard” using HTML inside st.markdown
card_html = f"""
<div class="news-card">
<div class="news-header">
<h4>πŸ—žοΈ News {i}</h4>
</div>
<div class="article-title">{article['title']}</div>
<div class="article-summary">{summary}</div>
<a class="read-link" href="{article['url']}" target="_blank">Read Full Article &raquo;</a>
</div>
"""
st.markdown(card_html, unsafe_allow_html=True)
# ───────────────────────────────────────────────────────────────────────────────
# Footer (optional)
st.markdown(
"""
<hr>
<div style="text-align:center; font-size:0.9rem; color:#6B7280; padding-top:1rem;">
Built with ❀️ using Streamlit β€’ Data sources: NewsAPI.org, Serper.dev β€’ Summarization via AWS Bedrock Claude
</div>
""",
unsafe_allow_html=True
)