|
import streamlit as st |
|
import pandas as pd |
|
import requests |
|
import json |
|
import base64 |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
import os |
|
from io import BytesIO |
|
from datetime import datetime |
|
|
|
|
|
st.set_page_config( |
|
page_title="News Summarization & Analysis", |
|
page_icon="π°", |
|
layout="wide", |
|
initial_sidebar_state="expanded" |
|
) |
|
import logging |
|
from flask import Flask |
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
app = Flask(__name__) |
|
|
|
@app.route('/') |
|
def home(): |
|
logging.info("Home endpoint was reached.") |
|
return "Hello, World!" |
|
|
|
if __name__ == '__main__': |
|
logging.info("Starting the Flask application.") |
|
app.run(host='0.0.0.0', port=7860) |
|
|
|
|
|
|
|
API_URL = "http://0.0.0.0:7860" |
|
|
|
def get_company_news(company_name): |
|
"""Fetch news articles for a given company via API""" |
|
try: |
|
response = requests.get(f"{API_URL}/news/{company_name}") |
|
if response.status_code == 200: |
|
return response.json() |
|
else: |
|
st.error(f"Error fetching news: {response.text}") |
|
return None |
|
except Exception as e: |
|
st.error(f"API connection error: {str(e)}") |
|
return None |
|
|
|
def get_analysis(company_name, articles): |
|
"""Get sentiment analysis and comparative analysis via API""" |
|
try: |
|
response = requests.post( |
|
f"{API_URL}/analyze", |
|
json={ |
|
"company": company_name, |
|
"articles": articles |
|
} |
|
) |
|
if response.status_code == 200: |
|
return response.json() |
|
else: |
|
st.error(f"Error analyzing content: {response.text}") |
|
return None |
|
except Exception as e: |
|
st.error(f"API connection error: {str(e)}") |
|
return None |
|
|
|
def get_tts(text, language='hi'): |
|
"""Get TTS audio in the specified language via API""" |
|
try: |
|
response = requests.post( |
|
f"{API_URL}/tts", |
|
json={ |
|
"text": text, |
|
"language": language |
|
} |
|
) |
|
if response.status_code == 200: |
|
return response.content, language |
|
else: |
|
st.error(f"Error generating speech: {response.text}") |
|
return None, language |
|
except Exception as e: |
|
st.error(f"API connection error: {str(e)}") |
|
return None, language |
|
|
|
def create_audio_player(audio_bytes): |
|
"""Create an HTML audio player for the TTS audio""" |
|
audio_base64 = base64.b64encode(audio_bytes).decode() |
|
audio_html = f""" |
|
<audio controls> |
|
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3"> |
|
Your browser does not support the audio element. |
|
</audio> |
|
""" |
|
return audio_html |
|
|
|
def display_article_details(articles): |
|
"""Display detailed information about each article in a card layout""" |
|
st.markdown(""" |
|
<style> |
|
.article-card { |
|
background-color: #f9f9f9; |
|
border-radius: 10px; |
|
padding: 20px; |
|
margin-bottom: 20px; |
|
border-left: 5px solid #4CAF50; |
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
|
} |
|
.article-negative { |
|
border-left: 5px solid #F44336; |
|
} |
|
.article-neutral { |
|
border-left: 5px solid #9E9E9E; |
|
} |
|
.article-title { |
|
font-size: 18px; |
|
font-weight: bold; |
|
margin-bottom: 10px; |
|
} |
|
.article-meta { |
|
color: #666; |
|
font-size: 14px; |
|
margin-bottom: 10px; |
|
} |
|
.article-summary { |
|
margin-bottom: 15px; |
|
} |
|
.article-sentiment { |
|
display: inline-block; |
|
padding: 5px 10px; |
|
border-radius: 20px; |
|
font-size: 14px; |
|
margin-right: 10px; |
|
} |
|
.sentiment-positive { |
|
background-color: rgba(76, 175, 80, 0.2); |
|
color: #2E7D32; |
|
} |
|
.sentiment-negative { |
|
background-color: rgba(244, 67, 54, 0.2); |
|
color: #C62828; |
|
} |
|
.sentiment-neutral { |
|
background-color: rgba(158, 158, 158, 0.2); |
|
color: #616161; |
|
} |
|
.topic-tag { |
|
display: inline-block; |
|
background-color: #E0E0E0; |
|
padding: 3px 10px; |
|
border-radius: 15px; |
|
margin-right: 8px; |
|
margin-bottom: 8px; |
|
font-size: 12px; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
cols = st.columns(1) |
|
|
|
for i, article in enumerate(articles): |
|
sentiment = article['Sentiment'] |
|
sentiment_class = "" |
|
tag_class = "" |
|
|
|
if sentiment == "Positive": |
|
sentiment_class = "article-positive" |
|
tag_class = "sentiment-positive" |
|
elif sentiment == "Negative": |
|
sentiment_class = "article-negative" |
|
tag_class = "sentiment-negative" |
|
else: |
|
sentiment_class = "article-neutral" |
|
tag_class = "sentiment-neutral" |
|
|
|
article_html = f""" |
|
<div class="article-card {sentiment_class}"> |
|
<div class="article-title">{article['Title']}</div> |
|
<div class="article-meta"> |
|
Source: {article.get('Source', 'Unknown')} | |
|
Date: {article.get('Date', 'N/A')} |
|
</div> |
|
<div class="article-summary">{article['Summary']}</div> |
|
<div class="article-sentiment {tag_class}">{sentiment}</div> |
|
""" |
|
|
|
|
|
if 'Topics' in article and article['Topics']: |
|
article_html += '<div class="article-topics">' |
|
for topic in article['Topics']: |
|
article_html += f'<span class="topic-tag">{topic}</span>' |
|
article_html += '</div>' |
|
|
|
article_html += f""" |
|
<div style="margin-top: 10px;"> |
|
<a href="{article.get('URL', '#')}" target="_blank" style="text-decoration: none;"> |
|
<span style="color: #1E88E5;">Read original article β</span> |
|
</a> |
|
</div> |
|
</div> |
|
""" |
|
|
|
cols[0].markdown(article_html, unsafe_allow_html=True) |
|
|
|
def display_sentiment_distribution(analysis): |
|
"""Display sentiment distribution chart with enhanced styling""" |
|
if 'Comparative Sentiment Score' in analysis and 'Sentiment Distribution' in analysis['Comparative Sentiment Score']: |
|
dist = analysis['Comparative Sentiment Score']['Sentiment Distribution'] |
|
data = { |
|
'Sentiment': list(dist.keys()), |
|
'Count': list(dist.values()) |
|
} |
|
df = pd.DataFrame(data) |
|
|
|
|
|
color_map = { |
|
'Positive': '#4CAF50', |
|
'Negative': '#F44336', |
|
'Neutral': '#9E9E9E' |
|
} |
|
|
|
|
|
st.markdown(""" |
|
<div style="background-color:white; padding:20px; border-radius:10px; box-shadow:0 4px 6px rgba(0,0,0,0.1); margin-bottom:20px;"> |
|
<h3 style="margin-bottom:15px; border-bottom:1px solid #eee; padding-bottom:10px;">Sentiment Distribution</h3> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
labels = list(dist.keys()) |
|
values = list(dist.values()) |
|
colors = [color_map[label] for label in labels] |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
fig_bar = px.bar( |
|
df, |
|
x='Sentiment', |
|
y='Count', |
|
color='Sentiment', |
|
color_discrete_map=color_map, |
|
title="Sentiment Distribution (Bar Chart)" |
|
) |
|
fig_bar.update_layout( |
|
plot_bgcolor='rgba(0,0,0,0)', |
|
paper_bgcolor='rgba(0,0,0,0)', |
|
font=dict(size=14), |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=350 |
|
) |
|
st.plotly_chart(fig_bar, use_container_width=True) |
|
|
|
with col2: |
|
|
|
fig_pie = go.Figure(data=[go.Pie( |
|
labels=labels, |
|
values=values, |
|
marker=dict(colors=colors), |
|
textinfo='percent+label', |
|
hole=.4 |
|
)]) |
|
fig_pie.update_layout( |
|
title_text="Sentiment Distribution (Pie Chart)", |
|
annotations=[dict(text='Sentiment', x=0.5, y=0.5, font_size=14, showarrow=False)], |
|
plot_bgcolor='rgba(0,0,0,0)', |
|
paper_bgcolor='rgba(0,0,0,0)', |
|
font=dict(size=14), |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=350 |
|
) |
|
st.plotly_chart(fig_pie, use_container_width=True) |
|
|
|
|
|
total = sum(values) |
|
if total > 0: |
|
percentages = {label: (count/total*100) for label, count in zip(labels, values)} |
|
|
|
|
|
summary_html = """ |
|
<div style="background-color:#f8f9fa; padding:15px; border-radius:8px; margin-top:10px;"> |
|
<h4 style="margin-bottom:10px;">Summary</h4> |
|
<p style="font-size:15px; line-height:1.5;"> |
|
""" |
|
|
|
for label in labels: |
|
if label in percentages: |
|
color = color_map[label] |
|
summary_html += f'<span style="color:{color}; font-weight:bold;">{label}</span>: {percentages[label]:.1f}% | ' |
|
|
|
summary_html = summary_html.rstrip(' | ') + '</p></div>' |
|
st.markdown(summary_html, unsafe_allow_html=True) |
|
|
|
def display_topic_analysis(analysis): |
|
"""Display topic analysis visualization""" |
|
if 'Comparative Sentiment Score' in analysis and 'Topic Overlap' in analysis['Comparative Sentiment Score']: |
|
topic_data = analysis['Comparative Sentiment Score']['Topic Overlap'] |
|
|
|
|
|
all_topics = set() |
|
if 'Common Topics' in topic_data: |
|
all_topics.update(topic_data['Common Topics']) |
|
|
|
for i in range(1, 11): |
|
key = f"Unique Topics in Article {i}" |
|
if key in topic_data and topic_data[key]: |
|
all_topics.update(topic_data[key]) |
|
|
|
|
|
topic_counts = {} |
|
for topic in all_topics: |
|
count = 0 |
|
if 'Common Topics' in topic_data and topic in topic_data['Common Topics']: |
|
count += len(analysis['Articles']) |
|
|
|
for i in range(1, 11): |
|
key = f"Unique Topics in Article {i}" |
|
if key in topic_data and topic in topic_data[key]: |
|
count += 1 |
|
|
|
topic_counts[topic] = count |
|
|
|
|
|
topic_df = pd.DataFrame({ |
|
'Topic': list(topic_counts.keys()), |
|
'Occurrence': list(topic_counts.values()) |
|
}).sort_values('Occurrence', ascending=False) |
|
|
|
fig = px.bar( |
|
topic_df, |
|
x='Topic', |
|
y='Occurrence', |
|
title="Topic Distribution Across Articles", |
|
color='Occurrence', |
|
color_continuous_scale=px.colors.sequential.Viridis |
|
) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
def display_comparative_analysis(analysis): |
|
"""Display comparative analysis details""" |
|
if 'Comparative Sentiment Score' in analysis and 'Coverage Differences' in analysis['Comparative Sentiment Score']: |
|
differences = analysis['Comparative Sentiment Score']['Coverage Differences'] |
|
|
|
st.subheader("Comparative Analysis") |
|
for i, diff in enumerate(differences): |
|
with st.expander(f"Comparison {i+1}"): |
|
st.write(f"**Comparison**: {diff['Comparison']}") |
|
st.write(f"**Impact**: {diff['Impact']}") |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.main-header { |
|
text-align: center; |
|
padding: 2rem 0; |
|
background: linear-gradient(to right, #2E7D32, #1565C0); |
|
color: white; |
|
border-radius: 10px; |
|
margin-bottom: 30px; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
|
} |
|
.app-title { |
|
font-size: 36px; |
|
font-weight: bold; |
|
text-shadow: 1px 1px 3px rgba(0,0,0,0.3); |
|
margin-bottom: 15px; |
|
} |
|
.app-description { |
|
font-size: 18px; |
|
color: white; |
|
max-width: 800px; |
|
margin: 0 auto; |
|
line-height: 1.6; |
|
text-shadow: 0px 1px 2px rgba(0,0,0,0.2); |
|
} |
|
.benefits-container { |
|
display: flex; |
|
justify-content: center; |
|
gap: 20px; |
|
margin-top: 20px; |
|
flex-wrap: wrap; |
|
} |
|
.benefit-item { |
|
background-color: rgba(255,255,255,0.25); |
|
padding: 8px 15px; |
|
border-radius: 20px; |
|
font-size: 14px; |
|
font-weight: 500; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
text-shadow: 0px 1px 1px rgba(0,0,0,0.1); |
|
} |
|
</style> |
|
|
|
<div class="main-header"> |
|
<div class="app-title">π° News Summarization & Sentiment Analysis</div> |
|
<p class="app-description"> |
|
Analyze recent news articles about any company. Get sentiment analysis, topic extraction, |
|
and multilingual text-to-speech summaries instantly in 10 different languages. |
|
</p> |
|
<div class="benefits-container"> |
|
<div class="benefit-item">β
Real-time News Analysis</div> |
|
<div class="benefit-item">π Sentiment Visualization</div> |
|
<div class="benefit-item">π Topic Extraction</div> |
|
<div class="benefit-item">π§ Multilingual Text-to-Speech</div> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.search-container { |
|
background-color: white; |
|
padding: 25px; |
|
border-radius: 10px; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
|
margin-bottom: 30px; |
|
} |
|
.search-title { |
|
font-size: 20px; |
|
font-weight: bold; |
|
margin-bottom: 15px; |
|
color: #333; |
|
} |
|
.search-description { |
|
color: #666; |
|
margin-bottom: 20px; |
|
font-size: 16px; |
|
} |
|
</style> |
|
<div class="search-container"> |
|
<div class="search-title">π Search for Company News</div> |
|
<div class="search-description"> |
|
Enter a company name below to analyze its recent news coverage. |
|
Try companies like Tesla, Apple, Microsoft, Google, or Amazon. |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
with st.form("search_form"): |
|
col1, col2 = st.columns([3, 1]) |
|
with col1: |
|
company_name = st.text_input("Company Name", placeholder="Enter company name (e.g., Tesla)", label_visibility="collapsed") |
|
with col2: |
|
submit_button = st.form_submit_button("π Analyze News") |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.example-row { |
|
display: flex; |
|
gap: 10px; |
|
margin-top: 10px; |
|
flex-wrap: wrap; |
|
justify-content: center; |
|
} |
|
.example-chip { |
|
background-color: #f0f2f6; |
|
border-radius: 20px; |
|
padding: 5px 15px; |
|
font-size: 12px; |
|
cursor: pointer; |
|
transition: all 0.2s; |
|
} |
|
.example-chip:hover { |
|
background-color: #4CAF50; |
|
color: white; |
|
} |
|
</style> |
|
<div style="text-align: center; margin-top: 10px; font-size: 12px; color: #666;"> |
|
Try analyzing news for: |
|
<div class="example-row"> |
|
<div class="example-chip">Tesla</div> |
|
<div class="example-chip">Apple</div> |
|
<div class="example-chip">Microsoft</div> |
|
<div class="example-chip">Google</div> |
|
<div class="example-chip">Amazon</div> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if submit_button and company_name: |
|
with st.spinner(f"Fetching news articles about {company_name}..."): |
|
articles_data = get_company_news(company_name) |
|
|
|
if articles_data and 'articles' in articles_data and len(articles_data['articles']) > 0: |
|
articles = articles_data['articles'] |
|
|
|
with st.spinner("Performing sentiment analysis..."): |
|
analysis_result = get_analysis(company_name, articles) |
|
|
|
if analysis_result: |
|
|
|
st.session_state.analysis = analysis_result |
|
|
|
|
|
st.header(f"Analysis Results for {company_name}") |
|
|
|
|
|
company_icon = "π’" |
|
if company_name.lower() == "tesla": |
|
company_icon = "π" |
|
elif company_name.lower() == "apple": |
|
company_icon = "π" |
|
elif company_name.lower() == "microsoft": |
|
company_icon = "π»" |
|
elif company_name.lower() == "amazon": |
|
company_icon = "π¦" |
|
elif company_name.lower() == "google": |
|
company_icon = "π" |
|
|
|
st.markdown(f""" |
|
<div style="background-color:#f0f2f6; padding:20px; border-radius:10px; margin-bottom:20px;"> |
|
<h1 style="text-align:center; margin-bottom:20px;">{company_icon} {company_name} News Analysis</h1> |
|
<p style="text-align:center; font-size:16px; color:#666;"> |
|
Analysis of {len(analysis_result['Articles'])} news articles | Generated on {datetime.now().strftime('%B %d, %Y')} |
|
</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.stTabs [data-baseweb="tab-list"] { |
|
gap: 8px; |
|
} |
|
.stTabs [data-baseweb="tab"] { |
|
border-radius: 4px 4px 0px 0px; |
|
padding: 10px 16px; |
|
background-color: #f0f2f6; |
|
} |
|
.stTabs [aria-selected="true"] { |
|
background-color: #4CAF50 !important; |
|
color: white !important; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
tab1, tab2, tab3, tab4 = st.tabs(["π Overview", "π Sentiment Analysis", "π Topic Analysis", "π° Article Details"]) |
|
|
|
with tab1: |
|
|
|
st.markdown(""" |
|
<div style="background-color:white; padding:25px; border-radius:10px; box-shadow:0 4px 6px rgba(0,0,0,0.1); margin-bottom:20px;"> |
|
<h3 style="margin-bottom:15px; border-bottom:1px solid #eee; padding-bottom:10px;">Executive Summary</h3> |
|
""", unsafe_allow_html=True) |
|
|
|
summary_text = analysis_result.get("Final Sentiment Analysis", "No summary available") |
|
st.markdown(f"<p style='font-size:16px; line-height:1.6;'>{summary_text}</p>", unsafe_allow_html=True) |
|
|
|
st.markdown("</div>", unsafe_allow_html=True) |
|
|
|
if "Final Sentiment Analysis" in analysis_result: |
|
|
|
language_options = { |
|
"hi": "Hindi", |
|
"en": "English", |
|
"es": "Spanish", |
|
"fr": "French", |
|
"de": "German", |
|
"ja": "Japanese", |
|
"zh-CN": "Chinese", |
|
"ru": "Russian", |
|
"ar": "Arabic", |
|
"it": "Italian" |
|
} |
|
|
|
selected_language = st.selectbox( |
|
"Select Language for Text-to-Speech", |
|
options=list(language_options.keys()), |
|
format_func=lambda x: language_options[x], |
|
index=0 |
|
) |
|
|
|
with st.spinner(f"Generating {language_options[selected_language]} text-to-speech..."): |
|
audio_bytes, language = get_tts(analysis_result["Final Sentiment Analysis"], selected_language) |
|
|
|
if audio_bytes: |
|
st.markdown(f""" |
|
<div style="background-color:white; padding:25px; border-radius:10px; box-shadow:0 4px 6px rgba(0,0,0,0.1);"> |
|
<h3 style="margin-bottom:15px; border-bottom:1px solid #eee; padding-bottom:10px;"> |
|
<span style="vertical-align:middle;">π</span> {language_options[language]} Text-to-Speech Summary |
|
</h3> |
|
""", unsafe_allow_html=True) |
|
|
|
st.markdown(create_audio_player(audio_bytes), unsafe_allow_html=True) |
|
st.markdown("</div>", unsafe_allow_html=True) |
|
|
|
with tab2: |
|
st.subheader("Sentiment Distribution") |
|
display_sentiment_distribution(analysis_result) |
|
display_comparative_analysis(analysis_result) |
|
|
|
with tab3: |
|
st.subheader("Topic Analysis") |
|
display_topic_analysis(analysis_result) |
|
|
|
with tab4: |
|
st.subheader("Article Details") |
|
display_article_details(analysis_result['Articles']) |
|
|
|
|
|
st.subheader("Raw JSON Output") |
|
with st.expander("Show JSON"): |
|
st.json(analysis_result) |
|
|
|
else: |
|
st.error("Failed to perform analysis. Please try again.") |
|
else: |
|
st.warning(f"No news articles found for {company_name}. Please try another company name.") |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.footer { |
|
background: linear-gradient(to right, #2E7D32, #1565C0); |
|
padding: 30px 10px; |
|
color: white; |
|
border-radius: 10px; |
|
text-align: center; |
|
margin-top: 40px; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
|
} |
|
.footer-content { |
|
max-width: 800px; |
|
margin: 0 auto; |
|
} |
|
.footer-title { |
|
font-size: 22px; |
|
margin-bottom: 15px; |
|
font-weight: bold; |
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.3); |
|
} |
|
.footer-text { |
|
font-size: 15px; |
|
line-height: 1.6; |
|
margin-bottom: 15px; |
|
text-shadow: 0px 1px 1px rgba(0,0,0,0.2); |
|
} |
|
.footer-features { |
|
display: flex; |
|
justify-content: center; |
|
gap: 15px; |
|
margin: 20px 0; |
|
flex-wrap: wrap; |
|
} |
|
.footer-feature { |
|
background-color: rgba(255,255,255,0.25); |
|
padding: 8px 15px; |
|
border-radius: 20px; |
|
font-size: 14px; |
|
font-weight: 500; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
text-shadow: 0px 1px 1px rgba(0,0,0,0.1); |
|
} |
|
.footer-copyright { |
|
margin-top: 15px; |
|
font-size: 14px; |
|
opacity: 0.9; |
|
text-shadow: 0px 1px 1px rgba(0,0,0,0.1); |
|
} |
|
</style> |
|
|
|
<div class="footer"> |
|
<div class="footer-content"> |
|
<div class="footer-title">π° News Summarization & Analysis Application</div> |
|
<div class="footer-text"> |
|
A powerful tool for analyzing news content, extracting sentiments, and generating insights. |
|
Get comprehensive analysis of any company's news coverage within seconds. |
|
</div> |
|
<div class="footer-features"> |
|
<div class="footer-feature">β‘ Real-time News Extraction</div> |
|
<div class="footer-feature">π Sentiment Analysis</div> |
|
<div class="footer-feature">π Topic Analysis</div> |
|
<div class="footer-feature">π§ Multilingual Text-to-Speech</div> |
|
</div> |
|
<div class="footer-copyright">Created with Streamlit β’ {datetime.now().year}</div> |
|
</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|