Dmitry Beresnev commited on
Commit
e7a399b
Β·
1 Parent(s): 7e1fe44

fix news feed

Browse files
Files changed (1) hide show
  1. app/components/news.py +93 -150
app/components/news.py CHANGED
@@ -7,24 +7,7 @@ import html as html_module
7
 
8
 
9
  def display_news_card(news_item: dict):
10
- """Display a single news card with professional styling."""
11
-
12
- # Sentiment color mapping
13
- sentiment_colors = {
14
- 'positive': '#10b981', # Green
15
- 'negative': '#ef4444', # Red
16
- 'neutral': '#6b7280' # Gray
17
- }
18
-
19
- # Impact badge styling
20
- impact_styles = {
21
- 'high': 'background: #fee2e2; color: #991b1b; border: 1px solid #fca5a5;',
22
- 'medium': 'background: #fef3c7; color: #92400e; border: 1px solid #fcd34d;',
23
- 'low': 'background: #dbeafe; color: #1e40af; border: 1px solid #93c5fd;'
24
- }
25
-
26
- sentiment_color = sentiment_colors.get(news_item['sentiment'], '#6b7280')
27
- impact_style = impact_styles.get(news_item['impact'], '')
28
 
29
  # Calculate time ago
30
  time_diff = datetime.now() - news_item['timestamp']
@@ -35,78 +18,52 @@ def display_news_card(news_item: dict):
35
  else:
36
  time_ago = f"{time_diff.seconds // 3600}h ago"
37
 
38
- # Breaking news indicator
39
- breaking_badge = ""
40
- if news_item.get('is_breaking', False):
41
- breaking_badge = """
42
- <span style='padding: 4px 8px; border-radius: 4px; font-size: 11px;
43
- font-weight: 700; background: #dc2626; color: white;
44
- margin-left: 8px; animation: pulse 2s infinite;'>
45
- πŸ”΄ BREAKING
46
- </span>
47
- """
48
-
49
- html = f"""
50
- <div style='background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
51
- border: 1px solid #374151; border-radius: 12px;
52
- padding: 20px; margin-bottom: 16px;
53
- transition: all 0.3s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);'>
54
-
55
- <div style='display: flex; justify-content: space-between; align-items: flex-start; gap: 20px;'>
56
- <div style='flex: 1;'>
57
- <!-- Header: Source + Badges -->
58
- <div style='display: flex; gap: 10px; margin-bottom: 12px; align-items: center; flex-wrap: wrap;'>
59
- <span style='color: #60a5fa; font-size: 14px; font-weight: 600;'>
60
- {news_item['source']}
61
- </span>
62
- <span style='padding: 4px 10px; border-radius: 6px; font-size: 11px;
63
- font-weight: 600; {impact_style} text-transform: uppercase;'>
64
- {news_item['impact']} IMPACT
65
- </span>
66
- <span style='display: inline-flex; align-items: center; gap: 6px;
67
- padding: 4px 10px; border-radius: 6px; font-size: 11px;
68
- background: rgba(107, 114, 128, 0.2); color: #9ca3af;'>
69
- <span style='width: 8px; height: 8px; border-radius: 50%;
70
- background: {sentiment_color};'></span>
71
- {news_item['sentiment'].title()}
72
- </span>
73
- <span style='padding: 4px 10px; border-radius: 6px; font-size: 11px;
74
- background: rgba(59, 130, 246, 0.1); color: #60a5fa;
75
- text-transform: uppercase;'>
76
- #{news_item['category']}
77
- </span>
78
- {breaking_badge}
79
- </div>
80
-
81
- <!-- Title -->
82
- <h3 style='color: #f3f4f6; margin: 0 0 12px 0; font-size: 17px;
83
- line-height: 1.5; font-weight: 600;'>
84
- {news_item.get('summary', '').strip()}
85
- </h3>
86
-
87
- <!-- Meta info -->
88
- <div style='color: #9ca3af; font-size: 13px; display: flex; gap: 16px; flex-wrap: wrap;'>
89
- <span>πŸ• {time_ago}</span>
90
- <span>❀️ {news_item['likes']:,}</span>
91
- <span>πŸ”„ {news_item['retweets']:,}</span>
92
- <span>⚑ Engagement: {(news_item['likes'] + news_item['retweets'] * 2):,}</span>
93
- </div>
94
- </div>
95
-
96
- <!-- Action button -->
97
- <a href='{news_item['url']}' target='_blank'
98
- style='background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
99
- color: white; padding: 10px 20px; border-radius: 8px;
100
- text-decoration: none; white-space: nowrap; font-size: 14px;
101
- font-weight: 600; box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
102
- transition: all 0.2s; display: inline-block;'>
103
- Read More β†’
104
- </a>
105
- </div>
106
- </div>
107
- """
108
-
109
- st.markdown(html, unsafe_allow_html=True)
110
 
111
 
112
  def display_news_feed(df: pd.DataFrame, max_items: int = 20):
@@ -168,7 +125,7 @@ def display_news_statistics(stats: dict):
168
 
169
 
170
  def display_category_breakdown(stats: dict):
171
- """Display news breakdown by category."""
172
 
173
  if 'by_category' not in stats:
174
  return
@@ -185,75 +142,61 @@ def display_category_breakdown(stats: dict):
185
  col1, col2, col3 = st.columns(3)
186
 
187
  with col1:
188
- macro_pct = (categories.get('macro', 0) / total) * 100
189
- st.markdown(f"""
190
- <div style='padding: 16px; background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
191
- border-radius: 10px; border: 1px solid #374151; text-align: center;'>
192
- <div style='color: #60a5fa; font-size: 14px; margin-bottom: 8px;'>πŸ“ˆ MACRO</div>
193
- <div style='color: #f3f4f6; font-size: 28px; font-weight: 700;'>
194
- {categories.get('macro', 0)}
195
- </div>
196
- <div style='color: #9ca3af; font-size: 12px;'>{macro_pct:.1f}% of total</div>
197
- </div>
198
- """, unsafe_allow_html=True)
199
 
200
  with col2:
201
- geo_pct = (categories.get('geopolitical', 0) / total) * 100
202
- st.markdown(f"""
203
- <div style='padding: 16px; background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
204
- border-radius: 10px; border: 1px solid #374151; text-align: center;'>
205
- <div style='color: #f59e0b; font-size: 14px; margin-bottom: 8px;'>🌍 GEOPOLITICAL</div>
206
- <div style='color: #f3f4f6; font-size: 28px; font-weight: 700;'>
207
- {categories.get('geopolitical', 0)}
208
- </div>
209
- <div style='color: #9ca3af; font-size: 12px;'>{geo_pct:.1f}% of total</div>
210
- </div>
211
- """, unsafe_allow_html=True)
212
 
213
  with col3:
214
- markets_pct = (categories.get('markets', 0) / total) * 100
215
- st.markdown(f"""
216
- <div style='padding: 16px; background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
217
- border-radius: 10px; border: 1px solid #374151; text-align: center;'>
218
- <div style='color: #10b981; font-size: 14px; margin-bottom: 8px;'>πŸ’Ή MARKETS</div>
219
- <div style='color: #f3f4f6; font-size: 28px; font-weight: 700;'>
220
- {categories.get('markets', 0)}
221
- </div>
222
- <div style='color: #9ca3af; font-size: 12px;'>{markets_pct:.1f}% of total</div>
223
- </div>
224
- """, unsafe_allow_html=True)
225
 
226
 
227
  def display_breaking_news_banner(df: pd.DataFrame):
228
- """Display breaking news banner at the top."""
229
 
230
  breaking = df[df['is_breaking'] == True] if not df.empty and 'is_breaking' in df.columns else pd.DataFrame()
231
 
232
  if not breaking.empty:
233
  latest = breaking.iloc[0]
234
 
235
- st.markdown(f"""
236
- <div style='background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
237
- border-radius: 12px; padding: 20px; margin-bottom: 24px;
238
- border: 2px solid #fca5a5; animation: pulse 3s infinite;
239
- box-shadow: 0 8px 16px rgba(220, 38, 38, 0.4);'>
240
- <div style='display: flex; align-items: center; gap: 16px;'>
241
- <span style='font-size: 32px;'>🚨</span>
242
- <div style='flex: 1;'>
243
- <div style='color: white; font-size: 12px; font-weight: 700;
244
- letter-spacing: 1px; margin-bottom: 8px;'>
245
- BREAKING NEWS β€’ {latest['source'].upper()}
246
- </div>
247
- <div style='color: white; font-size: 18px; font-weight: 600; line-height: 1.4;'>
248
- {latest.get('summary', '').strip()}
249
- </div>
250
- </div>
251
- <a href='{latest['url']}' target='_blank'
252
- style='background: white; color: #dc2626; padding: 12px 24px;
253
- border-radius: 8px; text-decoration: none; font-weight: 700;
254
- white-space: nowrap;'>
255
- READ NOW β†’
256
- </a>
257
- </div>
258
- </div>
259
- """, unsafe_allow_html=True)
 
 
7
 
8
 
9
  def display_news_card(news_item: dict):
10
+ """Display a single news card with professional styling using Streamlit components."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # Calculate time ago
13
  time_diff = datetime.now() - news_item['timestamp']
 
18
  else:
19
  time_ago = f"{time_diff.seconds // 3600}h ago"
20
 
21
+ # Create container with custom styling
22
+ with st.container():
23
+ # Add custom CSS for this card
24
+ st.markdown("""
25
+ <style>
26
+ .news-card {
27
+ background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
28
+ border: 1px solid #374151;
29
+ border-radius: 12px;
30
+ padding: 20px;
31
+ margin-bottom: 16px;
32
+ }
33
+ </style>
34
+ """, unsafe_allow_html=True)
35
+
36
+ # Header row with source and badges
37
+ col1, col2 = st.columns([3, 1])
38
+
39
+ with col1:
40
+ # Source and badges in one line
41
+ badge_cols = st.columns([2, 1, 1, 1])
42
+ with badge_cols[0]:
43
+ st.markdown(f"**:blue[{news_item['source']}]**")
44
+ with badge_cols[1]:
45
+ if news_item['impact'] == 'high':
46
+ st.markdown("πŸ”΄ **HIGH**")
47
+ elif news_item['impact'] == 'medium':
48
+ st.markdown("🟑 **MED**")
49
+ else:
50
+ st.markdown("🟒 **LOW**")
51
+ with badge_cols[2]:
52
+ sentiment_emoji = {'positive': 'πŸ“ˆ', 'negative': 'πŸ“‰', 'neutral': '➑️'}
53
+ st.markdown(f"{sentiment_emoji.get(news_item['sentiment'], '➑️')} {news_item['sentiment'].title()}")
54
+ with badge_cols[3]:
55
+ st.markdown(f"**#{news_item['category']}**")
56
+
57
+ with col2:
58
+ st.markdown(f"[**Read More β†’**]({news_item['url']})")
59
+
60
+ # Title/Summary
61
+ st.markdown(f"### {news_item.get('summary', '').strip()}")
62
+
63
+ # Meta information
64
+ st.caption(f"πŸ• {time_ago} | ❀️ {news_item['likes']:,} | πŸ”„ {news_item['retweets']:,}")
65
+
66
+ st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
 
69
  def display_news_feed(df: pd.DataFrame, max_items: int = 20):
 
125
 
126
 
127
  def display_category_breakdown(stats: dict):
128
+ """Display news breakdown by category using Streamlit components."""
129
 
130
  if 'by_category' not in stats:
131
  return
 
142
  col1, col2, col3 = st.columns(3)
143
 
144
  with col1:
145
+ macro_count = categories.get('macro', 0)
146
+ macro_pct = (macro_count / total) * 100
147
+ with st.container():
148
+ st.markdown("**:blue[πŸ“ˆ MACRO]**")
149
+ st.markdown(f"# {macro_count}")
150
+ st.caption(f"{macro_pct:.1f}% of total")
 
 
 
 
 
151
 
152
  with col2:
153
+ geo_count = categories.get('geopolitical', 0)
154
+ geo_pct = (geo_count / total) * 100
155
+ with st.container():
156
+ st.markdown("**:orange[🌍 GEOPOLITICAL]**")
157
+ st.markdown(f"# {geo_count}")
158
+ st.caption(f"{geo_pct:.1f}% of total")
 
 
 
 
 
159
 
160
  with col3:
161
+ markets_count = categories.get('markets', 0)
162
+ markets_pct = (markets_count / total) * 100
163
+ with st.container():
164
+ st.markdown("**:green[πŸ’Ή MARKETS]**")
165
+ st.markdown(f"# {markets_count}")
166
+ st.caption(f"{markets_pct:.1f}% of total")
 
 
 
 
 
167
 
168
 
169
  def display_breaking_news_banner(df: pd.DataFrame):
170
+ """Display breaking news banner at the top using Streamlit components."""
171
 
172
  breaking = df[df['is_breaking'] == True] if not df.empty and 'is_breaking' in df.columns else pd.DataFrame()
173
 
174
  if not breaking.empty:
175
  latest = breaking.iloc[0]
176
 
177
+ # Create container with red background styling
178
+ with st.container():
179
+ st.markdown("""
180
+ <style>
181
+ .breaking-news-container {
182
+ background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
183
+ border-radius: 12px;
184
+ padding: 20px;
185
+ margin-bottom: 24px;
186
+ border: 2px solid #fca5a5;
187
+ }
188
+ </style>
189
+ """, unsafe_allow_html=True)
190
+
191
+ # Layout with emoji and content
192
+ col1, col2, col3 = st.columns([1, 8, 2])
193
+
194
+ with col1:
195
+ st.markdown("# 🚨")
196
+
197
+ with col2:
198
+ st.markdown(f"**:red[BREAKING NEWS β€’ {latest['source'].upper()}]**")
199
+ st.markdown(f"## {latest.get('summary', '').strip()}")
200
+
201
+ with col3:
202
+ st.markdown(f"[**READ NOW β†’**]({latest['url']})")