News_Summarizer / app.py
MathWizard1729's picture
Update app.py
5b2c133 verified
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 (Dark Mode)",
page_icon="πŸ“°",
layout="wide", # wide layout for more room
initial_sidebar_state="expanded"
)
# ───────────────────────────────────────────────────────────────────────────────
# Custom CSS for dark UI
st.markdown(
"""
<style>
/* Set dark background for the entire app */
.stApp {
background-color: #121212;
color: #E0E0E0;
}
/* Sidebar background and text */
[data-testid="stSidebar"] {
background-color: #1E1E1E;
color: #E0E0E0;
}
[data-testid="stSidebar"] .stMarkdown,
[data-testid="stSidebar"] .stText,
[data-testid="stSidebar"] .stHeader {
color: #E0E0E0;
}
/* Change default input box background/text in dark mode */
.stTextInput > div > div > input {
background-color: #2A2A2A;
color: #E0E0E0;
border: 1px solid #444444;
}
/* Button styling */
.stButton > button {
background-color: #0078D7;
color: #FFFFFF;
border: none;
}
.stButton > button:hover {
background-color: #005FA1;
color: #FFFFFF;
}
/* Style for the title */
.app-title {
font-size: 2.5rem;
color: #BB86FC;
font-weight: 700;
margin-bottom: 0.2rem;
}
/* Style for the subtitle/description */
.app-subtitle {
font-size: 1.1rem;
color: #CCCCCC;
margin-bottom: 1.5rem;
}
/* Card container styling (dark card) */
.news-card {
background-color: #1F1F1F;
border-radius: 10px;
padding: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.5);
}
/* Header style inside each card (slightly lighter than card) */
.news-header {
background-color: #272727;
border-radius: 8px;
padding: 0.5rem 1rem;
margin-bottom: 0.8rem;
}
.news-header h4 {
margin: 0;
color: #BB86FC;
}
/* Article title style */
.article-title {
font-size: 1.2rem;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 0.5rem;
}
/* Summary text style */
.article-summary {
font-size: 1rem;
color: #CCCCCC;
margin-bottom: 0.7rem;
}
/* β€œRead more” link style */
.read-link {
font-size: 0.95rem;
color: #BB86FC;
text-decoration: none;
font-weight: 500;
}
.read-link:hover {
text-decoration: underline;
}
/* Spinner/loading text background override */
.stSpinner > div {
background-color: #1F1F1F !important;
}
/* Footer styling */
.footer-text {
text-align: center;
font-size: 0.9rem;
color: #888888;
padding-top: 1rem;
}
</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 (Dark Mode)</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:
# Use Markdown-style bold instead of raw HTML in st.info
st.info(f"πŸ” Searching for news on **{query}**...")
# Fetch articles
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 style="border-color: #333333; margin-top: 2rem;">
<div class="footer-text">
Built with ❀️ using Streamlit β€’ Data sources: NewsAPI.org, Serper.dev β€’ Summarization via AWS Bedrock Claude
</div>
""",
unsafe_allow_html=True
)