"""
AI News Sentiment Analyzer - Streamlit Web Application
Interactive dashboard for analyzing sentiment of AI-related news
"""
import streamlit as st
import pandas as pd
import plotly.express as px
import json
from api_handler import AINewsAnalyzer
import io
# Page configuration
st.set_page_config(
page_title="AI News Sentiment Analyzer",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for better styling
st.markdown("""
""", unsafe_allow_html=True)
# @st.cache_data(ttl=1800) # Cache for 30 minutes
def load_config():
"""Load configuration from config.json"""
with open('config.json', 'r') as f:
return json.load(f)
# @st.cache_data(ttl=1800) # Cache for 30 minutes
def load_news_data(query, days, sources=None, model="TextBlob"):
"""Load and cache news data"""
try:
analyzer = AINewsAnalyzer()
df = analyzer.get_ai_news_with_sentiment(query=query, days=days, sources=sources, model=model)
return df, None
except Exception as e:
return pd.DataFrame(), str(e)
def create_sentiment_distribution(df):
"""Create sentiment distribution pie chart"""
if df.empty:
return None
sentiment_counts = df['sentiment_label'].value_counts()
print("sentiment counts", sentiment_counts)
fig = px.pie(
values=sentiment_counts.values,
names=sentiment_counts.index,
title="🎯 Sentiment Distribution",
color_discrete_map={
'positive': '#28a745',
'negative': '#dc3545',
'neutral': '#6c757d'
}
)
fig.update_traces(textposition='inside', textinfo='percent+label')
return fig
def create_source_analysis(df):
"""Create source analysis chart"""
if df.empty:
return None
source_sentiment = df.groupby(['source', 'sentiment_label']).size().unstack(fill_value=0)
source_sentiment = source_sentiment.loc[source_sentiment.sum(axis=1).nlargest(10).index]
print("source Sentiment", source_sentiment)
fig = px.bar(
source_sentiment.reset_index(),
x='source',
y=['positive', 'negative', 'neutral'],
title="📰 Sentiment by News Source (Top 10)",
color_discrete_map={
'positive': '#28a745',
'negative': '#dc3545',
'neutral': '#6c757d'
}
)
fig.update_layout(
xaxis_title="News Source",
yaxis_title="Number of Articles",
xaxis_tickangle=-45
)
return fig
def create_polarity_distribution(df, thresh: float):
"""Create sentiment polarity distribution"""
if df.empty:
return None
fig = px.histogram(
df,
x='sentiment_polarity',
nbins=30,
title="📊 Sentiment Polarity Distribution",
labels={'sentiment_polarity': 'Sentiment Polarity', 'count': 'Number of Articles'}
)
# Add vertical lines for sentiment boundaries
fig.add_vline(x=thresh, line_dash="dash", line_color="green", annotation_text="Positive Threshold", annotation_position="top right")
fig.add_vline(x=-thresh, line_dash="dash", line_color="red", annotation_text="Negative Threshold", annotation_position="top left")
fig.add_vline(x=0, line_dash="dash", line_color="gray", annotation_text="Neutral", annotation_position="top")
return fig
def main():
# Header
st.markdown("
🤖 AI News Sentiment Analyzer
", unsafe_allow_html=True)
st.markdown("### Discover the sentiment trends in AI-related news from around the world")
# Load configuration
config = load_config()
# Sidebar controls
st.sidebar.header("🔧 Analysis Settings")
# Query input
query_options = config["search_queries"]
selected_query = st.sidebar.selectbox(
"📝 Search Topic:",
options=query_options,
index=0
)
custom_query = st.sidebar.text_input(
"Or enter custom search:",
placeholder="e.g., 'generative AI'"
)
model_query = st.sidebar.selectbox(
"📝 Search a Sentiment Model:",
options=config["model_options"],
index=0
)
# Use custom query if provided
final_query = custom_query if custom_query else selected_query
# Time range (days)
days = st.sidebar.slider(
"📅 Days to analyze:",
min_value=1,
max_value=30,
value=(7,14),
help="How many days back to search for news"
)
# # Date range filter (optional, after data is loaded)
# st.sidebar.markdown("---")
# st.sidebar.markdown("#### Optional: Filter by Date Range")
# News sources from config
news_sources = config["news_sources"]
source_option = st.sidebar.selectbox(
"📰 Source Category:",
options=config["source_categories"],
index=0
)
if source_option == "Tech Media":
sources = news_sources["tech_media"]
elif source_option == "General News":
sources = news_sources["general_news"]
elif source_option == "US News":
sources = news_sources["us_news"]
elif source_option == "Financial News":
sources = news_sources["financial_news"]
else:
sources = None
# Load data
if st.sidebar.button("🚀 Analyze News", type="primary"):
with st.spinner(f"Fetching and analyzing news about '{final_query}'..."):
df, error = load_news_data(final_query, days=days, sources=sources, model=model_query)
if error:
st.error(f"Error loading data: {error}")
st.stop()
if df.empty:
st.warning("No articles found. Try adjusting your search parameters.")
st.stop()
# Store results in session state
st.session_state.df = df
st.session_state.query = final_query
st.session_state.days = days
# ===== Display results if data is available =====
if 'df' in st.session_state and not st.session_state.df.empty:
df = st.session_state.df
print(df.info)
# ===== Summary Metrics =====
st.markdown("### 📊 Analysis Summary")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("📰 Total Articles", len(df))
with col2:
avg_polarity = df['sentiment_polarity'].mean()
delta_polarity = f"{avg_polarity:+.3f}"
st.metric("🎭 Avg Sentiment", f"{avg_polarity:.3f}", delta_polarity)
with col3:
positive_pct = (len(df[df['sentiment_label'] == 'positive']) / len(df) * 100)
st.metric("😊 Positive %", f"{positive_pct:.1f}%")
with col4:
unique_sources = df['source'].nunique()
st.metric("📺 News Sources", unique_sources)
# ===== Charts =====
st.markdown("### 📈 Visual Analysis")
col1, col2 = st.columns(2)
# Sentiment Distribution
dist_fig = create_sentiment_distribution(df)
if dist_fig:
st.plotly_chart(dist_fig, use_container_width=True, key="dist_fig")
# Export buttons
buf = io.BytesIO()
dist_fig.update_layout(template="plotly_white")
dist_fig.update_layout(plot_bgcolor='white', paper_bgcolor='white') # 设置白底
try:
dist_fig.write_image(buf, format="png", engine="kaleido")
except RuntimeError:
# Fallback if Chrome/Kaleido not available
html_buf = io.StringIO()
dist_fig.write_html(html_buf)
buf = io.BytesIO(html_buf.getvalue().encode('utf-8'))
st.download_button("📷 Download Distribution Chart as PNG", buf.getvalue(),
"distribution_chart.png", mime="image/png")
st.download_button("🌐 Download Distribution Chart as HTML",
dist_fig.to_html().encode("utf-8"), "distribution_chart.html",
mime="text/html")
# Source Analysis
source_fig = create_source_analysis(df)
if source_fig:
st.plotly_chart(source_fig, use_container_width=True, key="source_fig")
buf = io.BytesIO()
source_fig.update_layout(template="plotly_white")
source_fig.update_layout(plot_bgcolor='white', paper_bgcolor='white') # 白底
try:
source_fig.write_image(buf, format="png", engine="kaleido")
except RuntimeError:
# Fallback if Chrome/Kaleido not available
html_buf = io.StringIO()
source_fig.write_html(html_buf)
buf = io.BytesIO(html_buf.getvalue().encode('utf-8'))
st.download_button("📷 Download Source Chart as PNG", buf.getvalue(),
"source_chart.png", mime="image/png")
st.download_button("🌐 Download Source Chart as HTML",
source_fig.to_html().encode("utf-8"), "source_chart.html",
mime="text/html")
# Polarity Distribution
polarity_fig = create_polarity_distribution(df, 0.1)
if polarity_fig:
st.plotly_chart(polarity_fig, use_container_width=True, key="polarity_fig")
buf = io.BytesIO()
polarity_fig.update_layout(template="plotly_white")
polarity_fig.update_layout(plot_bgcolor='white', paper_bgcolor='white') # 白底
try:
polarity_fig.write_image(buf, format="png", engine="kaleido")
except RuntimeError:
# Fallback if Chrome/Kaleido not available
html_buf = io.StringIO()
polarity_fig.write_html(html_buf)
buf = io.BytesIO(html_buf.getvalue().encode('utf-8'))
st.download_button("📷 Download Polarity Chart as PNG", buf.getvalue(),
"polarity_chart.png", mime="image/png")
st.download_button("🌐 Download Polarity Chart as HTML",
polarity_fig.to_html().encode("utf-8"), "polarity_chart.html",
mime="text/html")
# ===== Export CSV button =====
csv_data = df.to_csv(index=False).encode('utf-8')
st.download_button(
label="💾 Export Analysis as CSV",
data=csv_data,
file_name=f"ai_news_analysis_{st.session_state.query.replace(' ', '_')}.csv",
mime='text/csv'
)
else:
# Welcome message
st.info("👋 Welcome! Configure your analysis settings in the sidebar and click 'Analyze News' to get started.")
# Sample visualization or instructions
st.markdown("""
### 🚀 How to Use:
1. **Choose a topic** from the dropdown or enter your own search term
2. **Select time range** (1-30 days) to analyze recent news
3. **Pick news sources** or leave as 'All Sources' for comprehensive coverage
4. **Click 'Analyze News'** to fetch and analyze articles
### 📊 What You'll Get:
- **Sentiment Analysis** of headlines and descriptions
- **Interactive Charts** showing trends over time
- **Source Breakdown** to see which outlets cover your topic
""")
pass
if __name__ == "__main__":
main()