Spaces:
Sleeping
Sleeping
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 | |
# Set page configuration | |
st.set_page_config( | |
page_title="News Summarization & Analysis", | |
page_icon="π°", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# API endpoint (Flask backend) | |
API_URL = "http://0.0.0.0:8000" | |
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> | |
""" | |
# Add topics as tags | |
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) | |
# Create color map | |
color_map = { | |
'Positive': '#4CAF50', | |
'Negative': '#F44336', | |
'Neutral': '#9E9E9E' | |
} | |
# Create a card container for the chart | |
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) | |
# Create pie chart for sentiment distribution | |
labels = list(dist.keys()) | |
values = list(dist.values()) | |
colors = [color_map[label] for label in labels] | |
# Create two columns for different chart types | |
col1, col2 = st.columns(2) | |
with col1: | |
# Bar chart | |
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: | |
# Pie chart | |
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) | |
# Add a summary of the sentiment distribution | |
total = sum(values) | |
if total > 0: | |
percentages = {label: (count/total*100) for label, count in zip(labels, values)} | |
# Create a summary card | |
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'] | |
# Prepare data for visualization | |
all_topics = set() | |
if 'Common Topics' in topic_data: | |
all_topics.update(topic_data['Common Topics']) | |
for i in range(1, 11): # Check for unique topics in each article | |
key = f"Unique Topics in Article {i}" | |
if key in topic_data and topic_data[key]: | |
all_topics.update(topic_data[key]) | |
# Count topic occurrences across articles | |
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']) # All articles have common topics | |
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 | |
# Create DataFrame and visualization | |
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']}") | |
# Main app layout with enhanced design and better readability | |
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) | |
# Input form with enhanced styling | |
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") | |
# Add some example buttons below the form | |
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) | |
# Process form submission | |
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: | |
# Store complete analysis in session state | |
st.session_state.analysis = analysis_result | |
# Display summary and stats | |
st.header(f"Analysis Results for {company_name}") | |
# Create a nice header with company logo or icon | |
company_icon = "π’" # Default 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) | |
# Display visualization tabs with custom styling | |
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: | |
# Create a card-style container for the summary | |
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 selection for TTS | |
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 # Default to Hindi | |
) | |
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']) | |
# Add video download tab | |
tab5 = st.tabs(["πΉ Demo Video"])[0] | |
with tab5: | |
st.subheader("Record and Download Demo") | |
if st.button("Record Demo (30s)"): | |
with st.spinner("Recording demo video..."): | |
try: | |
from record_demo import record_demo | |
record_demo() | |
st.success("Recording completed!") | |
# Display download button | |
with open('demo_video.mp4', 'rb') as video_file: | |
video_bytes = video_file.read() | |
st.download_button( | |
label="Download Demo Video", | |
data=video_bytes, | |
file_name="company_analysis_demo.mp4", | |
mime="video/mp4" | |
) | |
except Exception as e: | |
st.error(f"Error recording video: {str(e)}") | |
# Display JSON output option | |
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.") | |
# Footer with enhanced design and improved readability | |
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) | |