Spaces:
Sleeping
Sleeping
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 »</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 | |
) | |