Dmitry Beresnev commited on
Commit
5fbc88e
·
1 Parent(s): 2da1d42

fix news UI

Browse files
app/components/news.py CHANGED
@@ -373,120 +373,34 @@ def display_breaking_news_banner(df: pd.DataFrame):
373
  hours = time_diff.seconds // 3600
374
  time_ago = f"{hours}h ago" if hours < 24 else f"{time_diff.days}d ago"
375
 
376
- # TradingView-style breaking news banner
377
- banner_html = f"""
378
- <style>
379
- @keyframes pulse-glow {{
380
- 0%, 100% {{ box-shadow: 0 0 20px rgba(242, 54, 69, 0.6); }}
381
- 50% {{ box-shadow: 0 0 30px rgba(242, 54, 69, 0.9); }}
382
- }}
383
-
384
- @keyframes slide-in {{
385
- from {{ transform: translateX(-10px); opacity: 0; }}
386
- to {{ transform: translateX(0); opacity: 1; }}
387
- }}
388
- </style>
389
-
390
- <div style="
391
- background: linear-gradient(135deg, #F23645 0%, #C91B28 100%);
392
- border: 2px solid #FF6B78;
393
- border-radius: 12px;
394
- padding: 20px 24px;
395
- margin-bottom: 24px;
396
- animation: pulse-glow 2s ease-in-out infinite;
397
- position: relative;
398
- overflow: hidden;
399
- ">
400
- <!-- Animated background pattern -->
401
- <div style="
402
- position: absolute;
403
- top: 0;
404
- left: 0;
405
- right: 0;
406
- bottom: 0;
407
- background: repeating-linear-gradient(
408
- 45deg,
409
- transparent,
410
- transparent 10px,
411
- rgba(255, 255, 255, 0.03) 10px,
412
- rgba(255, 255, 255, 0.03) 20px
413
- );
414
- pointer-events: none;
415
- "></div>
416
-
417
- <!-- Content -->
418
- <div style="position: relative; z-index: 1;">
419
- <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 12px;">
420
- <!-- Animated icon -->
421
- <div style="
422
- font-size: 32px;
423
- animation: pulse-glow 1s ease-in-out infinite;
424
- filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.3));
425
- ">🚨</div>
426
-
427
- <!-- Header -->
428
- <div style="flex: 1;">
429
- <div style="
430
- color: white;
431
- font-size: 14px;
432
- font-weight: 700;
433
- letter-spacing: 1.5px;
434
- text-transform: uppercase;
435
- margin-bottom: 4px;
436
- font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif;
437
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
438
- ">⚡ Breaking News</div>
439
- <div style="
440
- color: rgba(255, 255, 255, 0.9);
441
- font-size: 11px;
442
- display: flex;
443
- align-items: center;
444
- gap: 8px;
445
- ">
446
- <span style="
447
- background: rgba(255, 255, 255, 0.2);
448
- padding: 2px 8px;
449
- border-radius: 4px;
450
- font-weight: 600;
451
- ">{source}</span>
452
- <span style="opacity: 0.8;">•</span>
453
- <span style="opacity: 0.8;">{time_ago}</span>
454
- </div>
455
- </div>
456
-
457
- <!-- Read button -->
458
- <a href="{url}" target="_blank" style="
459
- background: white;
460
- color: #F23645;
461
- padding: 10px 20px;
462
- border-radius: 6px;
463
- font-size: 13px;
464
- font-weight: 700;
465
- text-decoration: none;
466
- display: inline-flex;
467
- align-items: center;
468
- gap: 6px;
469
- transition: all 0.2s ease;
470
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
471
- " onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.3)';"
472
- onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 8px rgba(0, 0, 0, 0.2)';">
473
- READ NOW →
474
- </a>
475
- </div>
476
-
477
- <!-- News summary -->
478
- <div style="
479
- color: white;
480
- font-size: 16px;
481
- font-weight: 500;
482
- line-height: 1.5;
483
- margin-left: 48px;
484
- font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif;
485
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
486
- animation: slide-in 0.5s ease-out;
487
- ">{summary}</div>
488
- </div>
489
- </div>
490
- """
491
 
492
  st.markdown(banner_html, unsafe_allow_html=True)
 
373
  hours = time_diff.seconds // 3600
374
  time_ago = f"{hours}h ago" if hours < 24 else f"{time_diff.days}d ago"
375
 
376
+ # TradingView-style breaking news banner (no leading whitespace)
377
+ banner_html = f"""<style>
378
+ @keyframes pulse-glow {{
379
+ 0%, 100% {{ box-shadow: 0 0 20px rgba(242, 54, 69, 0.6); }}
380
+ 50% {{ box-shadow: 0 0 30px rgba(242, 54, 69, 0.9); }}
381
+ }}
382
+ @keyframes slide-in {{
383
+ from {{ transform: translateX(-10px); opacity: 0; }}
384
+ to {{ transform: translateX(0); opacity: 1; }}
385
+ }}
386
+ </style>
387
+ <div style="background: linear-gradient(135deg, #F23645 0%, #C91B28 100%); border: 2px solid #FF6B78; border-radius: 12px; padding: 20px 24px; margin-bottom: 24px; animation: pulse-glow 2s ease-in-out infinite; position: relative; overflow: hidden;">
388
+ <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255, 255, 255, 0.03) 10px, rgba(255, 255, 255, 0.03) 20px); pointer-events: none;"></div>
389
+ <div style="position: relative; z-index: 1;">
390
+ <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 12px;">
391
+ <div style="font-size: 32px; animation: pulse-glow 1s ease-in-out infinite; filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.3));">🚨</div>
392
+ <div style="flex: 1;">
393
+ <div style="color: white; font-size: 14px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; margin-bottom: 4px; font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);">⚡ Breaking News</div>
394
+ <div style="color: rgba(255, 255, 255, 0.9); font-size: 11px; display: flex; align-items: center; gap: 8px;">
395
+ <span style="background: rgba(255, 255, 255, 0.2); padding: 2px 8px; border-radius: 4px; font-weight: 600;">{source}</span>
396
+ <span style="opacity: 0.8;">•</span>
397
+ <span style="opacity: 0.8;">{time_ago}</span>
398
+ </div>
399
+ </div>
400
+ <a href="{url}" target="_blank" style="background: white; color: #F23645; padding: 10px 20px; border-radius: 6px; font-size: 13px; font-weight: 700; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.3)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 8px rgba(0, 0, 0, 0.2)';">READ NOW →</a>
401
+ </div>
402
+ <div style="color: white; font-size: 16px; font-weight: 500; line-height: 1.5; margin-left: 48px; font-family: -apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); animation: slide-in 0.5s ease-out;">{summary}</div>
403
+ </div>
404
+ </div>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
406
  st.markdown(banner_html, unsafe_allow_html=True)
app/services/twitter_news_playwright.py CHANGED
@@ -208,8 +208,8 @@ class TwitterFinanceMonitor:
208
  logger.warning("Chromium not found in standard paths")
209
  return '/usr/bin/chromium' # Fallback
210
 
211
- def _scrape_twitter_profile(self, source_name: str, source_info: Dict, timeout: int = 12) -> List[Dict]:
212
- """Scrape tweets from a single Twitter profile using Playwright (optimized for speed)"""
213
  if not PLAYWRIGHT_AVAILABLE:
214
  logger.warning("Playwright not available")
215
  return []
@@ -234,7 +234,7 @@ class TwitterFinanceMonitor:
234
  )
235
  page = context.new_page()
236
 
237
- # Block images, fonts, css, and videos for maximum speed
238
  def route_intercept(route):
239
  if route.request.resource_type in ["image", "media", "font", "stylesheet", "video"]:
240
  route.abort()
@@ -243,19 +243,19 @@ class TwitterFinanceMonitor:
243
 
244
  page.route("**/*", route_intercept)
245
 
246
- # Navigate to profile with reduced timeout
247
  logger.info(f"Scraping {source_name}...")
248
- page.goto(source_info['url'], timeout=timeout * 1000, wait_until="domcontentloaded") # Don't wait for full load
249
 
250
- # Wait for tweets to load with reduced timeout
251
  try:
252
- page.wait_for_selector("article", timeout=8000) # Fixed 8 second wait
253
  except PlaywrightTimeoutError:
254
  logger.warning(f"Timeout waiting for tweets from {source_name}")
255
  browser.close()
256
  return []
257
 
258
- # Extract tweet texts (limit to 15 for speed)
259
  tweet_elements = page.locator("article div[data-testid='tweetText']").all()
260
 
261
  news_items = []
@@ -331,19 +331,19 @@ class TwitterFinanceMonitor:
331
  reverse=True
332
  )
333
 
334
- # Scrape sources in parallel with higher concurrency
335
- # 8 workers = 19 sources in 3 batches (~30-45 seconds total)
336
  with ThreadPoolExecutor(max_workers=8) as executor:
337
  futures = []
338
  for name, info in sorted_sources:
339
- # Reduced timeout for faster failures
340
- future = executor.submit(_self._scrape_twitter_profile, name, info, timeout=12)
341
  futures.append((future, name))
342
 
343
  for future, source_name in futures:
344
  try:
345
- # Wait max 15 seconds per source (down from 20)
346
- news_items = future.result(timeout=15)
347
 
348
  # Deduplicate based on text similarity
349
  unique_items = []
 
208
  logger.warning("Chromium not found in standard paths")
209
  return '/usr/bin/chromium' # Fallback
210
 
211
+ def _scrape_twitter_profile(self, source_name: str, source_info: Dict, timeout: int = 30) -> List[Dict]:
212
+ """Scrape tweets from a single Twitter profile using Playwright"""
213
  if not PLAYWRIGHT_AVAILABLE:
214
  logger.warning("Playwright not available")
215
  return []
 
234
  )
235
  page = context.new_page()
236
 
237
+ # Block images, fonts, css, and videos for speed
238
  def route_intercept(route):
239
  if route.request.resource_type in ["image", "media", "font", "stylesheet", "video"]:
240
  route.abort()
 
243
 
244
  page.route("**/*", route_intercept)
245
 
246
+ # Navigate to profile with increased timeout
247
  logger.info(f"Scraping {source_name}...")
248
+ page.goto(source_info['url'], timeout=timeout * 1000, wait_until="domcontentloaded")
249
 
250
+ # Wait for tweets to load with increased timeout
251
  try:
252
+ page.wait_for_selector("article", timeout=15000) # Increased to 15 seconds
253
  except PlaywrightTimeoutError:
254
  logger.warning(f"Timeout waiting for tweets from {source_name}")
255
  browser.close()
256
  return []
257
 
258
+ # Extract tweet texts (limit to 15)
259
  tweet_elements = page.locator("article div[data-testid='tweetText']").all()
260
 
261
  news_items = []
 
331
  reverse=True
332
  )
333
 
334
+ # Scrape sources in parallel with moderate concurrency
335
+ # 8 workers = 19 sources in 3 batches (~60-90 seconds total)
336
  with ThreadPoolExecutor(max_workers=8) as executor:
337
  futures = []
338
  for name, info in sorted_sources:
339
+ # Increased timeout for better success rate
340
+ future = executor.submit(_self._scrape_twitter_profile, name, info, timeout=30)
341
  futures.append((future, name))
342
 
343
  for future, source_name in futures:
344
  try:
345
+ # Wait max 35 seconds per source (increased for reliability)
346
+ news_items = future.result(timeout=35)
347
 
348
  # Deduplicate based on text similarity
349
  unique_items = []