Spaces:
Running
Running
import streamlit as st | |
import pandas as pd | |
from typing import Union, List, Dict | |
from groq import Groq | |
import os | |
from duckduckgo_search import DDGS | |
# Set page configuration with fullscreen layout and custom theme | |
st.set_page_config( | |
page_title="Z-Alpha News Analysis", | |
layout="wide", | |
initial_sidebar_state="collapsed", | |
page_icon="π" | |
) | |
# Execute JavaScript to make the app fullscreen on load | |
st.markdown(""" | |
<script> | |
document.addEventListener('DOMContentLoaded', (event) => { | |
// Request fullscreen on page load | |
document.documentElement.requestFullscreen().catch(e => { | |
console.log("Fullscreen request failed: ", e); | |
}); | |
}); | |
</script> | |
""", unsafe_allow_html=True) | |
# Enhanced CSS for a more beautiful Google DeepMind-inspired styling with green background | |
st.markdown(""" | |
<style> | |
/* Base styles */ | |
html, body, .stApp, .main { | |
background-color: #000000 !important; | |
color: #ffffff !important; | |
} | |
/* Typography */ | |
h1, h2, h3, h4, h5, h6 { | |
color: #00ff00 !important; | |
font-family: 'Courier New', monospace !important; | |
} | |
p, div, span { | |
color: #cccccc !important; | |
} | |
/* Cards and containers */ | |
.card { | |
background-color: #0a0a0a !important; | |
border: 1px solid #00ff00 !important; | |
border-radius: 4px !important; | |
box-shadow: 0 0 15px rgba(0, 255, 0, 0.2) !important; | |
} | |
/* Analysis results */ | |
.analysis-result { | |
background-color: #121212 !important; | |
border-left: 4px solid #00ff00 !important; | |
color: #ffffff !important; | |
font-family: 'Courier New', monospace !important; | |
padding: 1.5rem !important; | |
} | |
/* Input fields */ | |
.stTextInput input { | |
background-color: #121212 !important; | |
color: #00ff00 !important; | |
border: 1px solid #00ff00 !important; | |
border-radius: 0 !important; | |
} | |
/* Buttons */ | |
.stButton > button { | |
background: linear-gradient(45deg, #00ff00, #003300) !important; | |
color: #000000 !important; | |
border: none !important; | |
border-radius: 0 !important; | |
font-family: 'Courier New', monospace !important; | |
font-weight: bold !important; | |
transition: all 0.3s ease !important; | |
} | |
.stButton > button:hover { | |
box-shadow: 0 0 20px #00ff00 !important; | |
transform: scale(1.05) !important; | |
} | |
/* Source items */ | |
.source-item { | |
border-bottom: 1px solid #333333 !important; | |
padding: 1rem 0 !important; | |
} | |
.source-title { | |
color: #00ff00 !important; | |
font-family: 'Courier New', monospace !important; | |
} | |
/* Links */ | |
a { | |
color: #00ff00 !important; | |
text-decoration: underline !important; | |
} | |
a:hover { | |
color: #99ff99 !important; | |
} | |
/* Status indicators */ | |
.status-badge { | |
background-color: #002200 !important; | |
color: #00ff00 !important; | |
border: 1px solid #00ff00 !important; | |
} | |
/* Tabs */ | |
.stTabs [aria-selected="true"] { | |
background-color: #121212 !important; | |
color: #00ff00 !important; | |
border-bottom: 2px solid #00ff00 !important; | |
} | |
/* Logo styling */ | |
.logo-text { | |
background: linear-gradient(90deg, #00ff00, #99ff99) !important; | |
-webkit-background-clip: text !important; | |
-webkit-text-fill-color: transparent !important; | |
text-shadow: 0 0 10px rgba(0, 255, 0, 0.5) !important; | |
} | |
/* Icons */ | |
.float-icon svg { | |
filter: drop-shadow(0 0 5px #00ff00); | |
} | |
/* Scrollbar styling */ | |
::-webkit-scrollbar { | |
width: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: #000000; | |
} | |
::-webkit-scrollbar-thumb { | |
background: #00ff00; | |
border-radius: 4px; | |
} | |
/* Dropdown menus */ | |
.stSelectbox div[data-baseweb="select"] div { | |
background-color: #121212 !important; | |
color: #00ff00 !important; | |
border: 1px solid #00ff00 !important; | |
border-radius: 0 !important; | |
} | |
/* Sliders */ | |
.stSlider .thumb { | |
background-color: #00ff00 !important; | |
} | |
.stSlider .track { | |
background-color: #003300 !important; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
class DuckDuckGoSearch: | |
""" | |
Custom DuckDuckGo search implementation with robust error handling and result processing. | |
Uses the duckduckgo_search library to fetch and format news results. | |
""" | |
def __init__(self): | |
# Initialize the DuckDuckGo search session | |
self.ddgs = DDGS() | |
def __call__(self, query: str, max_results: int = 5) -> str: | |
try: | |
# Perform the search and get results | |
# The news method is more appropriate for recent news analysis | |
search_results = list(self.ddgs.news( | |
query, | |
max_results=max_results, | |
region='wt-wt', # Worldwide results | |
safesearch='on' | |
)) | |
if not search_results: | |
return "No results found. Try modifying your search query." | |
# Format the results into a readable string | |
formatted_results = [] | |
for idx, result in enumerate(search_results, 1): | |
# Extract available fields with fallbacks for missing data | |
title = result.get('title', 'No title available') | |
snippet = result.get('body', result.get('snippet', 'No description available')) | |
source = result.get('source', 'Unknown source') | |
url = result.get('url', result.get('link', 'No link available')) | |
date = result.get('date', 'Date not available') | |
# Format each result with available information | |
formatted_results.append( | |
f"{idx}. Title: {title}\n" | |
f" Date: {date}\n" | |
f" Source: {source}\n" | |
f" Summary: {snippet}\n" | |
f" URL: {url}\n" | |
) | |
return "\n".join(formatted_results) | |
except Exception as e: | |
# Provide detailed error information for debugging | |
error_msg = f"Search error: {str(e)}\nTry again with a different search term or check your internet connection." | |
print(f"DuckDuckGo search error: {str(e)}") # For logging | |
return error_msg | |
class GroqLLM: | |
""" | |
LLM interface using Groq's LLama model. | |
Handles API communication and response processing. | |
""" | |
def __init__(self, model_name="llama-3.1-8B-Instant"): | |
self.client = Groq(api_key=os.environ.get("GROQ_API_KEY")) | |
self.model_name = model_name | |
def __call__(self, prompt: Union[str, dict, List[Dict]]) -> str: | |
try: | |
# Convert prompt to string if it's a complex structure | |
prompt_str = str(prompt) if isinstance(prompt, (dict, list)) else prompt | |
# Make API call to Groq | |
completion = self.client.chat.completions.create( | |
model=self.model_name, | |
messages=[{ | |
"role": "user", | |
"content": prompt_str | |
}], | |
temperature=0.7, | |
max_tokens=1024, | |
stream=False | |
) | |
return completion.choices[0].message.content if completion.choices else "Error: No response generated" | |
except Exception as e: | |
error_msg = f"Error generating response: {str(e)}" | |
print(error_msg) # For logging | |
return error_msg | |
def create_analysis_prompt(topic: str, search_results: str) -> str: | |
""" | |
Creates a detailed prompt for news analysis, structuring the request | |
to get comprehensive and well-organized results from the LLM. | |
""" | |
return f"""Analyze the following news information about {topic}. | |
Search Results: {search_results} | |
Please provide a comprehensive analysis including: | |
1. Key Points Summary: | |
- Main events and developments | |
- Critical updates and changes | |
2. Stakeholder Analysis: | |
- Primary parties involved | |
- Their roles and positions | |
3. Impact Assessment: | |
- Immediate implications | |
- Potential long-term effects | |
- Broader context and significance | |
4. Multiple Perspectives: | |
- Different viewpoints on the issue | |
- Areas of agreement and contention | |
5. Fact Check & Reliability: | |
- Verification of major claims | |
- Consistency across sources | |
- Source credibility assessment | |
Please format the analysis in a clear, journalistic style with section headers.""" | |
def log_agent_activity(prompt: str, result: str, agent_name: str): | |
""" | |
Creates an expandable log of agent activities in the Streamlit interface | |
for transparency and debugging purposes. | |
""" | |
with st.expander("π View Agent Activity Log"): | |
st.markdown("<h4>Agent Activity Log</h4>", unsafe_allow_html=True) | |
st.markdown(f"<p><strong>Agent:</strong> {agent_name}</p>", unsafe_allow_html=True) | |
st.markdown("<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 8px; margin-bottom: 1rem;'>", unsafe_allow_html=True) | |
st.markdown("<p><strong>Input Prompt:</strong></p>", unsafe_allow_html=True) | |
st.code(prompt, language="text") | |
st.markdown("</div>", unsafe_allow_html=True) | |
st.markdown("<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 8px;'>", unsafe_allow_html=True) | |
st.markdown("<p><strong>Analysis Output:</strong></p>", unsafe_allow_html=True) | |
st.code(result, language="text") | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Animated header with branded logo and description | |
st.markdown(""" | |
<div class="card" style="padding-bottom: 1.5rem;"> | |
<div style="display: flex; align-items: center; gap: 1.5rem;"> | |
<div class="float-icon" style="text-align: center;"> | |
<svg width="70" height="70" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
<path d="M19.5 9.5l-7.5-7.5-7.5 7.5m15 0v8c0 1.1-.9 2-2 2h-11c-1.1 0-2-.9-2-2v-8" stroke="#1a73e8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
<circle cx="12" cy="12" r="3" stroke="#1a73e8" stroke-width="2"/> | |
<path d="M12 15v3" stroke="#1a73e8" stroke-width="2" stroke-linecap="round"/> | |
</svg> | |
</div> | |
<div style="flex: 1;"> | |
<span class="logo-text">Z-Agent News Analysis</span> | |
<p style="color: #5f6368; font-size: 1.1rem; margin-top: 0.5rem;"> | |
Intelligent news analysis powered by AI. Get comprehensive insights and multiple perspectives on any topic. | |
</p> | |
</div> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Main content area | |
st.markdown('<div class="card">', unsafe_allow_html=True) | |
# Initialize the components | |
try: | |
# Initialize LLM and search tool | |
llm = GroqLLM() | |
search_tool = DuckDuckGoSearch() | |
# Input section with enhanced design | |
st.markdown("<h2 style='margin-top: 0;'>News Topic Analysis</h2>", unsafe_allow_html=True) | |
news_topic = st.text_input( | |
"What news topic would you like to analyze?", | |
placeholder="E.g., Recent developments in renewable energy, Tech industry layoffs, Global climate agreements...", | |
key="news_topic_input" | |
) | |
# Analysis options in a cleaner layout | |
st.markdown("<div style='margin-top: 1.5rem;'></div>", unsafe_allow_html=True) | |
st.markdown("<h3 style='margin-bottom: 1rem;'>Analysis Options</h3>", unsafe_allow_html=True) | |
col1, col2, col3 = st.columns([2, 2, 2]) | |
with col1: | |
st.markdown("<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 12px; height: 100%;'>", unsafe_allow_html=True) | |
st.markdown("<p style='font-weight: 600; color: #202124; margin-bottom: 0.5rem;'>Search Depth</p>", unsafe_allow_html=True) | |
search_depth = st.slider( | |
"##", | |
min_value=3, | |
max_value=10, | |
value=5, | |
help="Number of news sources to analyze", | |
key="search_depth_slider", | |
label_visibility="collapsed" | |
) | |
st.markdown("<p style='color: #5f6368; font-size: 0.9rem;'>Number of sources: " + str(search_depth) + "</p>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
with col2: | |
st.markdown("<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 12px; height: 100%;'>", unsafe_allow_html=True) | |
st.markdown("<p style='font-weight: 600; color: #202124; margin-bottom: 0.5rem;'>Analysis Type</p>", unsafe_allow_html=True) | |
analysis_type = st.selectbox( | |
"##", | |
["Comprehensive", "Quick Summary", "Technical", "Simplified"], | |
help="Choose the style and depth of analysis", | |
key="analysis_type_select", | |
label_visibility="collapsed" | |
) | |
st.markdown("<p style='color: #5f6368; font-size: 0.9rem;'>Selected: " + analysis_type + "</p>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
with col3: | |
st.markdown("<div style='background-color: #f8f9fa; padding: 1rem; border-radius: 12px; height: 100%;'>", unsafe_allow_html=True) | |
st.markdown("<p style='font-weight: 600; color: #202124; margin-bottom: 0.5rem;'>Time Period</p>", unsafe_allow_html=True) | |
time_period = st.selectbox( | |
"##", | |
["Last 7 days", "Last 30 days", "Last 24 hours"], | |
help="How recent should the news be", | |
key="time_period_select", | |
label_visibility="collapsed" | |
) | |
st.markdown("<p style='color: #5f6368; font-size: 0.9rem;'>Selected: " + time_period + "</p>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
# Generate analysis button | |
st.markdown("<div style='margin-top: 2rem;'></div>", unsafe_allow_html=True) | |
col1, col2, col3 = st.columns([2, 2, 2]) | |
with col2: | |
analyze_button = st.button("ANALYZE NEWS") | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Results section | |
if analyze_button and news_topic: | |
with st.spinner(""): | |
try: | |
# Progress indicators with enhanced styling | |
progress_container = st.container() | |
progress_container.markdown('<div class="info-box">', unsafe_allow_html=True) | |
progress_placeholder = progress_container.empty() | |
progress_placeholder.markdown(""" | |
<div class="pulsing"> | |
<div class="status-badge status-searching"> | |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
<circle cx="11" cy="11" r="8"></circle> | |
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> | |
</svg> | |
<span>Searching for recent news sources...</span> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Determine time frame from selection | |
time_map = { | |
"Last 24 hours": "24h", | |
"Last 7 days": "7d", | |
"Last 30 days": "30d" | |
} | |
time_frame = time_map.get(time_period, "7d") | |
# Perform search | |
search_results = search_tool( | |
f"Latest news about {news_topic} {time_frame}", | |
max_results=search_depth | |
) | |
if not search_results.startswith(("Search error", "No results")): | |
# Update progress | |
progress_placeholder.markdown(""" | |
<div class="pulsing"> | |
<div class="status-badge status-analyzing"> | |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
<path d="M12 3v3m0 12v3m-9-9H3m3-6l2 2M3 12h3m12 0h3m-3-6l-2 2m9 3h-3m-6 6l-2 2m12 0l-2-2"></path> | |
</svg> | |
<span>Analyzing search results and generating insights...</span> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Create analysis prompt | |
analysis_prompt = create_analysis_prompt(news_topic, search_results) | |
# Get analysis from LLM | |
analysis_result = llm(analysis_prompt) | |
# Clear progress messages | |
progress_container.empty() | |
# Display results in tabs for better organization | |
st.markdown('<div class="card">', unsafe_allow_html=True) | |
st.markdown(f'<h2>Analysis: {news_topic}</h2>', unsafe_allow_html=True) | |
tab1, tab2 = st.tabs(["π Analysis", "π° Sources"]) | |
with tab1: | |
st.markdown('<div class="analysis-result">', unsafe_allow_html=True) | |
st.markdown(analysis_result) | |
st.markdown('</div>', unsafe_allow_html=True) | |
with tab2: | |
st.markdown("<h3>News Sources Used in Analysis</h3>", unsafe_allow_html=True) | |
# Parse and display sources in a more structured way | |
sources = search_results.split("\n\n") | |
for source in sources: | |
lines = source.strip().split("\n") | |
if len(lines) >= 5: # Ensure we have enough lines | |
title_line = lines[0].replace("1. Title: ", "").replace("2. Title: ", "").replace("3. Title: ", "").replace("4. Title: ", "").replace("5. Title: ", "") | |
date_line = lines[1].replace(" Date: ", "") | |
source_line = lines[2].replace(" Source: ", "") | |
url_line = lines[4].replace(" URL: ", "") | |
st.markdown(f""" | |
<div class="source-item"> | |
<p class="source-title">{title_line}</p> | |
<div class="source-meta"> | |
<span>{source_line}</span> | |
<span class="source-meta-dot"></span> | |
<span>{date_line}</span> | |
</div> | |
<a href="{url_line}" class="read-link" target="_blank"> | |
Read original article | |
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path> | |
<path d="M15 3h6v6"></path> | |
<path d="M10 14L21 3"></path> | |
</svg> | |
</a> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Log the activity | |
log_agent_activity( | |
analysis_prompt, | |
analysis_result, | |
"News Analysis Agent" | |
) | |
else: | |
progress_container.empty() | |
st.error(f""" | |
<div style="display: flex; align-items: center; gap: 12px;"> | |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FF5252" stroke-width="2"> | |
<circle cx="12" cy="12" r="10"></circle> | |
<line x1="12" y1="8" x2="12" y2="12"></line> | |
<line x1="12" y1="16" x2="12.01" y2="16"></line> | |
</svg> | |
<span>{search_results}</span> | |
</div> | |
""", unsafe_allow_html=True) | |
except Exception as e: | |
st.error(f""" | |
<div style="display: flex; align-items: center; gap: 12px;"> | |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FF5252" stroke-width="2"> | |
<circle cx="12" cy="12" r="10"></circle> | |
<line x1="12" y1="8" x2="12" y2="12"></line> | |
<line x1="12" y1="16" x2="12.01" y2="16"></line> | |
</svg> | |
<span>An error occurred during analysis: {str(e)}</span> | |
</div> | |
""", unsafe_allow_html=True) | |
elif analyze_button: | |
# This handles the case when analyze button is clicked but no topic is entered | |
st.warning(""" | |
<div style="display: flex; align-items: center; gap: 12px;"> | |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFA726" stroke-width="2"> | |
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"></path> | |
<line x1="12" y1="9" x2="12" y2="13"></line> | |
<line x1="12" y1="17" x2="12.01" y2="17"></line> | |
</svg> | |
<span>Please enter a news topic to analyze.</span> | |
</div> | |
""", unsafe_allow_html=True) | |
# Tips and usage guidance section - displayed when no analysis is in progress | |
if not analyze_button or not news_topic: | |
st.markdown('<div class="card">', unsafe_allow_html=True) | |
st.markdown("<h2 style='margin-top: 0;'>Tips for Better Results</h2>", unsafe_allow_html=True) | |
st.markdown(""" | |
<div class="tip-item"> | |
<div class="tip-icon">π‘</div> | |
<div class="tip-text"> | |
<strong>Be specific with your topic.</strong> Instead of "climate change", try "recent climate legislation in the EU". | |
</div> | |
</div> | |
<div class="tip-item"> | |
<div class="tip-icon">π</div> | |
<div class="tip-text"> | |
<strong>Adjust search depth</strong> to find the right balance between comprehensive coverage and analysis speed. | |
</div> | |
</div> | |
<div class="tip-item"> | |
<div class="tip-icon">π</div> | |
<div class="tip-text"> | |
<strong>Choose the right analysis type</strong> based on your needs: | |
<ul> | |
<li><strong>Comprehensive:</strong> Full analysis with multiple perspectives</li> | |
<li><strong>Quick Summary:</strong> Brief overview of key points</li> | |
<li><strong>Technical:</strong> Detailed analysis with industry-specific terminology</li> | |
<li><strong>Simplified:</strong> Easy-to-understand breakdown of complex topics</li> | |
</ul> | |
</div> | |
</div> | |
<div class="tip-item"> | |
<div class="tip-icon">β±οΈ</div> | |
<div class="tip-text"> | |
<strong>Select an appropriate time period</strong> based on how recent you want the news to be. | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Example topics for quick selection | |
st.markdown('<div class="card">', unsafe_allow_html=True) | |
st.markdown("<h2 style='margin-top: 0;'>Try These Topics</h2>", unsafe_allow_html=True) | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
if st.button("π Climate Change Policies"): | |
st.session_state.news_topic_input = "Recent climate change policies and agreements" | |
st.experimental_rerun() | |
with col2: | |
if st.button("π° Cryptocurrency Trends"): | |
st.session_state.news_topic_input = "Latest developments in cryptocurrency markets" | |
st.experimental_rerun() | |
with col3: | |
if st.button("π¬ AI Research Breakthroughs"): | |
st.session_state.news_topic_input = "Recent breakthroughs in artificial intelligence research" | |
st.experimental_rerun() | |
st.markdown('</div>', unsafe_allow_html=True) | |
# Footer with attribution and version info | |
st.markdown(""" | |
<footer> | |
<div style="display: flex; justify-content: space-between; align-items: center;"> | |
<div>Z-Agent News Analysis v1.0.1</div> | |
<div></div> | |
</div> | |
</footer> | |
""", unsafe_allow_html=True) | |
except Exception as e: | |
# Global error handling to catch any unforeseen issues | |
st.error(f""" | |
<div style="display: flex; align-items: center; gap: 12px;"> | |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FF5252" stroke-width="2"> | |
<circle cx="12" cy="12" r="10"></circle> | |
<line x1="12" y1="8" x2="12" y2="12"></line> | |
<line x1="12" y1="16" x2="12.01" y2="16"></line> | |
</svg> | |
<span>Application Error: {str(e)}</span> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown(""" | |
<div style="background-color: #F8F9FA; padding: 1rem; border-radius: 8px; margin-top: 1rem;"> | |
<p><strong>Troubleshooting Tips:</strong></p> | |
<ul> | |
<li>Check your API keys for Groq LLM in environment variables</li> | |
<li>Ensure you have internet connection for DuckDuckGo searches</li> | |
<li>Try refreshing the page or restarting the application</li> | |
</ul> | |
</div> | |
""", unsafe_allow_html=True) | |
# Add a hidden feature to reset the application state | |
if st.sidebar.button("Reset Application", key="reset_app"): | |
for key in st.session_state.keys(): | |
del st.session_state[key] | |
st.experimental_rerun() | |
# Optional: Add a feedback mechanism | |
with st.sidebar: | |
st.markdown("### Feedback") | |
feedback = st.text_area("Share your thoughts or report issues", placeholder="Your feedback helps us improve...") | |
if st.button("Submit Feedback"): | |
st.success("Thank you for your feedback!") | |
# In a production app, you would save this feedback to a database |