|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="AI News Sentiment Analyzer", |
|
|
page_icon="๐ค", |
|
|
layout="wide", |
|
|
initial_sidebar_state="expanded" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
.main-header { |
|
|
font-size: 2.5rem; |
|
|
font-weight: bold; |
|
|
color: #1f77b4; |
|
|
text-align: center; |
|
|
margin-bottom: 2rem; |
|
|
} |
|
|
.metric-card { |
|
|
background-color: #f0f2f6; |
|
|
padding: 1rem; |
|
|
border-radius: 0.5rem; |
|
|
border-left: 5px solid #1f77b4; |
|
|
} |
|
|
.positive { color: #28a745; } |
|
|
.negative { color: #dc3545; } |
|
|
.neutral { color: #6c757d; } |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
def load_config(): |
|
|
"""Load configuration from config.json""" |
|
|
with open('config.json', 'r') as f: |
|
|
return json.load(f) |
|
|
|
|
|
|
|
|
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'} |
|
|
) |
|
|
|
|
|
|
|
|
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(): |
|
|
|
|
|
st.markdown("<h1 class='main-header'>๐ค AI News Sentiment Analyzer</h1>", unsafe_allow_html=True) |
|
|
st.markdown("### Discover the sentiment trends in AI-related news from around the world") |
|
|
|
|
|
|
|
|
config = load_config() |
|
|
|
|
|
|
|
|
st.sidebar.header("๐ง Analysis Settings") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
final_query = custom_query if custom_query else selected_query |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
st.session_state.df = df |
|
|
st.session_state.query = final_query |
|
|
st.session_state.days = days |
|
|
|
|
|
|
|
|
if 'df' in st.session_state and not st.session_state.df.empty: |
|
|
df = st.session_state.df |
|
|
print(df.info) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
st.markdown("### ๐ Visual Analysis") |
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
|
|
|
dist_fig = create_sentiment_distribution(df) |
|
|
if dist_fig: |
|
|
st.plotly_chart(dist_fig, use_container_width=True, key="dist_fig") |
|
|
|
|
|
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: |
|
|
|
|
|
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_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: |
|
|
|
|
|
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_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: |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
st.info("๐ Welcome! Configure your analysis settings in the sidebar and click 'Analyze News' to get started.") |
|
|
|
|
|
|
|
|
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() |