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(
"""
""",
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('
📰 News Summarizer Agent (Dark Mode)
', unsafe_allow_html=True)
st.markdown(
'Get the top 5 latest news with concise summaries on any topic or company you choose.
',
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"""
"""
st.markdown(card_html, unsafe_allow_html=True)
# ───────────────────────────────────────────────────────────────────────────────
# Footer (optional)
st.markdown(
"""
""",
unsafe_allow_html=True
)