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