Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
import time
|
4 |
+
from utils import fetch_news, analyze_sentiment, extract_topics, generate_tts
|
5 |
+
import plotly.express as px
|
6 |
+
import warnings
|
7 |
+
warnings.filterwarnings("ignore", category=UserWarning)
|
8 |
+
|
9 |
+
# Add custom CSS
|
10 |
+
st.markdown("""
|
11 |
+
<style>
|
12 |
+
h1, .stTitle {color: #2E86C1; font-size: 2.5em; font-weight: bold;}
|
13 |
+
h2, .stSubheader {color: #1A5276; font-weight: bold;}
|
14 |
+
.stButton>button {background-color: #3498DB; color: white; border-radius: 5px; padding: 0.5em 1em;}
|
15 |
+
.stButton>button:hover {background-color: #2E86C1;}
|
16 |
+
.sentiment-positive {color: green; font-weight: bold;}
|
17 |
+
.sentiment-negative {color: red; font-weight: bold;}
|
18 |
+
.sentiment-neutral {color: gray; font-weight: bold;}
|
19 |
+
.sidebar .sidebar-content {position: sticky; top: 50%; padding: 10px;}
|
20 |
+
</style>
|
21 |
+
""", unsafe_allow_html=True)
|
22 |
+
|
23 |
+
st.title("News Summarizer and Sentiment Analyzer")
|
24 |
+
|
25 |
+
company_name = st.text_input("Enter a company name to get a sentiment report of its recent news.", placeholder="e.g., Google, Meta", value="")
|
26 |
+
|
27 |
+
if st.button("Analyze"):
|
28 |
+
with st.spinner("Fetching and analyzing news articles..."):
|
29 |
+
time.sleep(1)
|
30 |
+
articles_data = fetch_news(company_name)
|
31 |
+
if not articles_data:
|
32 |
+
st.error(f"No articles found for {company_name}. Check logs for details.")
|
33 |
+
else:
|
34 |
+
articles = []
|
35 |
+
sentiments = {"Positive": 0, "Negative": 0, "Neutral": 0}
|
36 |
+
positive_articles = []
|
37 |
+
negative_articles = []
|
38 |
+
neutral_articles = []
|
39 |
+
|
40 |
+
for article in articles_data:
|
41 |
+
summary = article["summary"].strip() or article["title"].split(" - ")[0].strip()
|
42 |
+
source = article["title"].split(" - ")[-1].strip() if " - " in article["title"] else ""
|
43 |
+
if source in summary:
|
44 |
+
summary = summary.replace(source, "").strip()
|
45 |
+
summary = f"{summary.rstrip(' -')} - {source}"
|
46 |
+
|
47 |
+
sentiment = analyze_sentiment(summary)
|
48 |
+
topics = extract_topics(summary)
|
49 |
+
sentiments[sentiment] += 1
|
50 |
+
|
51 |
+
title = article["title"].split(" - ")[0].strip()
|
52 |
+
if sentiment == "Positive":
|
53 |
+
positive_articles.append(title)
|
54 |
+
elif sentiment == "Negative":
|
55 |
+
negative_articles.append(title)
|
56 |
+
else:
|
57 |
+
neutral_articles.append(title)
|
58 |
+
|
59 |
+
articles.append({
|
60 |
+
"Title": article["title"],
|
61 |
+
"Summary": summary,
|
62 |
+
"Sentiment": sentiment,
|
63 |
+
"Topics": topics,
|
64 |
+
"Link": article["link"],
|
65 |
+
"PubDate": article["pub_date"]
|
66 |
+
})
|
67 |
+
|
68 |
+
import random
|
69 |
+
detailed_comparisons = [f"- News {i + 1} {article['Sentiment'].lower()}ly discusses {', '.join(article['Topics'])}"
|
70 |
+
for i, article in enumerate(articles)]
|
71 |
+
dominant_sentiment = max(sentiments, key=sentiments.get)
|
72 |
+
trends = f"{company_name} News Trends: {dominant_sentiment}"
|
73 |
+
|
74 |
+
total_articles = sum(sentiments.values())
|
75 |
+
sentiment_count = f"{sentiments['Positive']} positive, {sentiments['Negative']} negative, {sentiments['Neutral']} neutral"
|
76 |
+
|
77 |
+
intro_phrases = [
|
78 |
+
f"Spanning {total_articles} recent reports, the narrative surrounding {company_name} tilts {dominant_sentiment.lower()}, with {sentiment_count}.",
|
79 |
+
f"Across {total_articles} articles in recent coverage, {company_name}’s story emerges as predominantly {dominant_sentiment.lower()}, reflecting {sentiment_count}.",
|
80 |
+
f"Drawing from {total_articles} latest publications, {company_name}’s news landscape leans {dominant_sentiment.lower()}, underscored by {sentiment_count}."
|
81 |
+
]
|
82 |
+
positive_phrases = [
|
83 |
+
f"With {len(positive_articles)} favorable accounts, {company_name} demonstrates notable progress, exemplified by '{random.choice(positive_articles) if positive_articles else 'no specific examples available'}'.",
|
84 |
+
f"Boasting {len(positive_articles)} positive developments, {company_name} showcases strength, as evidenced in '{random.choice(positive_articles) if positive_articles else 'no notable instances'}'.",
|
85 |
+
f"Highlighted by {len(positive_articles)} encouraging reports, {company_name} is forging ahead, with '{random.choice(positive_articles) if positive_articles else 'no standout reports'}' standing out."
|
86 |
+
]
|
87 |
+
negative_phrases = [
|
88 |
+
f"However, {len(negative_articles)} troubling narratives raise concerns, including '{random.choice(negative_articles) if negative_articles else 'no specific concerns noted'}'.",
|
89 |
+
f"Yet, {len(negative_articles)} adverse reports signal challenges, such as '{random.choice(negative_articles) if negative_articles else 'no highlighted issues'}'.",
|
90 |
+
f"Nevertheless, {len(negative_articles)} concerning stories cast a shadow, notably '{random.choice(negative_articles) if negative_articles else 'no notable setbacks'}'."
|
91 |
+
]
|
92 |
+
neutral_phrases = [
|
93 |
+
f"Additionally, {len(neutral_articles)} impartial updates provide context, such as '{random.choice(neutral_articles) if neutral_articles else 'no neutral updates available'}'.",
|
94 |
+
f"Meanwhile, {len(neutral_articles)} balanced accounts offer insight, including '{random.choice(neutral_articles) if neutral_articles else 'no balanced reports'}'.",
|
95 |
+
f"Furthermore, {len(neutral_articles)} objective pieces contribute details, like '{random.choice(neutral_articles) if neutral_articles else 'no objective details'}'."
|
96 |
+
]
|
97 |
+
outlook_phrases_positive = [
|
98 |
+
f"In summary, {company_name} appears poised for a favorable trajectory.",
|
99 |
+
f"All told, {company_name} stands on the cusp of a promising future.",
|
100 |
+
f"Ultimately, {company_name} is positioned for an optimistic course ahead."
|
101 |
+
]
|
102 |
+
outlook_phrases_negative = [
|
103 |
+
f"In conclusion, {company_name} confronts a challenging path forward.",
|
104 |
+
f"Overall, {company_name} navigates a formidable road ahead.",
|
105 |
+
f"To conclude, {company_name} faces a demanding horizon."
|
106 |
+
]
|
107 |
+
outlook_phrases_mixed = [
|
108 |
+
f"In the final analysis, {company_name} balances opportunity and uncertainty.",
|
109 |
+
f"On balance, {company_name} presents a complex outlook moving forward.",
|
110 |
+
f"Ultimately, {company_name} reflects a blend of prospects and hurdles."
|
111 |
+
]
|
112 |
+
|
113 |
+
final_text = random.choice(intro_phrases) + " "
|
114 |
+
if positive_articles:
|
115 |
+
final_text += random.choice(positive_phrases) + " "
|
116 |
+
if negative_articles:
|
117 |
+
final_text += random.choice(negative_phrases) + " "
|
118 |
+
if neutral_articles:
|
119 |
+
final_text += random.choice(neutral_phrases) + " "
|
120 |
+
if sentiments["Positive"] > sentiments["Negative"]:
|
121 |
+
final_text += random.choice(outlook_phrases_positive)
|
122 |
+
elif sentiments["Negative"] > sentiments["Positive"]:
|
123 |
+
final_text += random.choice(outlook_phrases_negative)
|
124 |
+
else:
|
125 |
+
final_text += random.choice(outlook_phrases_mixed)
|
126 |
+
|
127 |
+
st.session_state.result = {
|
128 |
+
"Company": company_name,
|
129 |
+
"Articles": articles,
|
130 |
+
"Comparative Sentiment Score": {
|
131 |
+
"Sentiment Distribution": f"Positive: {sentiments['Positive']}, Negative: {sentiments['Negative']}, Neutral: {sentiments['Neutral']}",
|
132 |
+
"Trends": trends,
|
133 |
+
"Detailed Comparisons": "\n".join(detailed_comparisons)
|
134 |
+
},
|
135 |
+
"Final Sentiment Analysis": final_text.strip()
|
136 |
+
}
|
137 |
+
|
138 |
+
if "result" in st.session_state:
|
139 |
+
result = st.session_state.result
|
140 |
+
if "error" in result:
|
141 |
+
st.error(result["error"])
|
142 |
+
else:
|
143 |
+
dist = result['Comparative Sentiment Score']['Sentiment Distribution']
|
144 |
+
sentiment_counts = {
|
145 |
+
"Positive": int(dist.split("Positive: ")[1].split(",")[0]),
|
146 |
+
"Negative": int(dist.split("Negative: ")[1].split(",")[0]),
|
147 |
+
"Neutral": int(dist.split("Neutral: ")[1])
|
148 |
+
}
|
149 |
+
fig = px.pie(
|
150 |
+
values=list(sentiment_counts.values()),
|
151 |
+
names=list(sentiment_counts.keys()),
|
152 |
+
title="Sentiment Distribution",
|
153 |
+
color_discrete_map={"Positive": "green", "Negative": "red", "Neutral": "gray"},
|
154 |
+
width=300,
|
155 |
+
height=300
|
156 |
+
)
|
157 |
+
fig.update_layout(margin=dict(t=40, b=0, l=0, r=0))
|
158 |
+
st.sidebar.plotly_chart(fig, use_container_width=True)
|
159 |
+
|
160 |
+
st.subheader(f"Analysis for {result['Company']}")
|
161 |
+
for i, article in enumerate(result["Articles"], 1):
|
162 |
+
st.write(f"**News {i}:** {article['PubDate']} [Read full article]({article['Link']})")
|
163 |
+
st.write(f"Summary: {article['Summary']}")
|
164 |
+
sentiment_class = f"sentiment-{article['Sentiment'].lower()}"
|
165 |
+
st.markdown(f"Sentiment: <span class='{sentiment_class}'>{article['Sentiment']}</span>", unsafe_allow_html=True)
|
166 |
+
st.write("")
|
167 |
+
|
168 |
+
st.subheader("Comparative Sentiment Analysis")
|
169 |
+
st.write("Detailed Comparisons:")
|
170 |
+
st.write(f"Sentiment Distribution: {result['Comparative Sentiment Score']['Sentiment Distribution']}")
|
171 |
+
st.markdown(f"**{result['Comparative Sentiment Score']['Trends']}**", unsafe_allow_html=True)
|
172 |
+
st.markdown(result["Comparative Sentiment Score"]["Detailed Comparisons"], unsafe_allow_html=True)
|
173 |
+
|
174 |
+
st.subheader("Final Sentiment Analysis")
|
175 |
+
st.write(result["Final Sentiment Analysis"])
|
176 |
+
|
177 |
+
language = st.selectbox("Select Audio Language", ["Hindi", "English"])
|
178 |
+
if st.button("Generate News Audio"):
|
179 |
+
with st.spinner("Generating audio..."):
|
180 |
+
audio_buffer = generate_tts(result["Final Sentiment Analysis"], 'hi' if language == "Hindi" else 'en')
|
181 |
+
if audio_buffer:
|
182 |
+
st.audio(audio_buffer, format="audio/mp3")
|
183 |
+
else:
|
184 |
+
st.error("Failed to generate audio. Check terminal logs.")
|
185 |
+
|
186 |
+
st.markdown("""
|
187 |
+
<p style="font-size: small; color: grey; text-align: center;">
|
188 |
+
Developed By: Krishna Prakash
|
189 |
+
<a href="https://www.linkedin.com/in/krishnaprakash-profile/" target="_blank">
|
190 |
+
<img src="https://img.icons8.com/ios-filled/30/0077b5/linkedin.png" alt="LinkedIn" style="vertical-align: middle; margin: 0 5px;"/>
|
191 |
+
</a>
|
192 |
+
</p>
|
193 |
+
""", unsafe_allow_html=True)
|