ALwrity Version 0.5.1 (Fastapi + React)

This commit is contained in:
ajaysi
2025-08-06 16:29:49 +05:30
parent dbf761c31f
commit 2579c12ba4
331 changed files with 0 additions and 22 deletions

View File

@@ -0,0 +1,169 @@
# AI Web Researcher Dashboard for Content Creators
## Overview
The AI Web Researcher Dashboard is a comprehensive suite of research tools designed specifically for content creators. This dashboard integrates various web research modules to streamline the content research process, enhance content quality, and improve workflow efficiency.
## Available Research Modules
### 1. Search Engine Research Tools
#### Google SERP Search
- **Functionality**: Retrieves organic search results, People Also Ask questions, and related searches
- **Best for**: Initial topic research, understanding search intent, and identifying content gaps
- **Key features**: Comprehensive search results with structured data extraction
#### Tavily AI Search
- **Functionality**: Advanced AI-powered search with semantic understanding
- **Best for**: In-depth research requiring contextual understanding
- **Key features**: Provides direct answers to questions and follow-up question suggestions
### 2. Neural Search Tools
#### Metaphor Neural Search
- **Functionality**: Semantic search technology for finding related content
- **Best for**: Discovering content based on conceptual similarity rather than keyword matching
- **Key features**: Find similar articles, competitor analysis, and content inspiration
### 3. Trend Analysis Tools
#### Google Trends Researcher
- **Functionality**: Analyzes search term popularity over time
- **Best for**: Content planning, seasonal topic identification, and trend forecasting
- **Key features**: Interest over time visualization, regional interest mapping, and related query analysis
### 4. Web Crawling & Analysis Tools
#### Async Web Crawler
- **Functionality**: Crawls websites to extract structured content
- **Best for**: Content auditing, competitor analysis, and data extraction
- **Key features**: Extracts titles, descriptions, main content, headings, links, and images
#### Firecrawl Web Crawler
- **Functionality**: Specialized crawler for single-page or website scraping
- **Best for**: Detailed content extraction from specific URLs
- **Key features**: Configurable depth and page limits for targeted crawling
#### Website Analyzer
- **Functionality**: Comprehensive website analysis for content and technical aspects
- **Best for**: Content quality assessment, SEO analysis, and technical audits
- **Key features**: Content quality metrics, SEO recommendations, and technical performance insights
## Optimal Workflows for Content Creators
### Research Workflow 1: Comprehensive Topic Research
1. **Initial Exploration** (Google SERP Search)
- Search for your main topic to understand the search landscape
- Analyze People Also Ask questions to identify user intent
2. **In-depth Research** (Tavily AI Search)
- Use identified subtopics for deeper research
- Collect factual information and expert insights
3. **Competitive Analysis** (Metaphor Neural Search + Web Crawler)
- Find similar content from competitors
- Analyze content structure and approach
4. **Trend Validation** (Google Trends Researcher)
- Verify topic popularity and seasonal patterns
- Identify related trending topics
### Research Workflow 2: Content Gap Analysis
1. **Competitor Content Mapping** (Async Web Crawler)
- Crawl competitor websites to extract content structure
- Identify their content categories and topics
2. **Topic Opportunity Discovery** (Google Trends + Metaphor Search)
- Research trending topics in your niche
- Find content areas with high interest but low competition
3. **Content Quality Benchmarking** (Website Analyzer)
- Analyze top-performing content in your niche
- Identify quality benchmarks and content standards
### Research Workflow 3: Content Refresh & Update
1. **Content Performance Analysis** (Website Analyzer)
- Analyze existing content for improvement opportunities
- Identify outdated information and gaps
2. **Current Trend Integration** (Google Trends Researcher)
- Research current trends related to your content
- Identify new angles and perspectives
3. **Competitive Edge Research** (Tavily AI + Metaphor Search)
- Research what competitors have updated recently
- Find new research, statistics, and information to incorporate
## Integration with Content Creation Process
### Pre-Writing Phase
- Use research modules to gather comprehensive information
- Create content briefs based on research findings
- Identify key points, statistics, and examples to include
### Writing Phase
- Reference research findings for accurate information
- Use competitor insights to differentiate your content
- Incorporate trending topics and keywords
### Post-Publishing Phase
- Monitor content performance against researched benchmarks
- Update content based on new research findings
- Plan related content based on research insights
## Dashboard Usage Examples
### Example 1: Creating a Comprehensive Blog Post
1. Start with Google SERP Search for your main topic
2. Use Tavily AI Search to gather in-depth information on subtopics
3. Analyze competitor content with Metaphor Neural Search
4. Check topic seasonality with Google Trends Researcher
5. Create content that addresses all aspects discovered in research
### Example 2: Developing a Content Strategy
1. Use Google Trends to identify trending topics in your niche
2. Analyze competitor websites with Async Web Crawler
3. Research content gaps using Metaphor Neural Search
4. Prioritize content topics based on trend data and competition
5. Create a content calendar incorporating research insights
### Example 3: Updating Existing Content
1. Analyze current content with Website Analyzer
2. Research new developments with Tavily AI Search
3. Check for changing trends with Google Trends Researcher
4. Identify new angles with Metaphor Neural Search
5. Update content with new information and perspectives
## Best Practices for Effective Research
1. **Start Broad, Then Narrow**: Begin with general search tools before using specialized ones
2. **Combine Multiple Research Methods**: Use different modules for comprehensive insights
3. **Focus on User Intent**: Prioritize research that reveals what your audience wants
4. **Validate with Data**: Use trend analysis to validate topic importance
5. **Organize Research Findings**: Create structured notes from your research
6. **Set Research Goals**: Define what you need to learn before starting
7. **Schedule Regular Research**: Keep content updated with periodic research
## Technical Requirements
- API keys for various services (Google, Tavily, Metaphor, etc.)
- Proper configuration in the .env file
- Sufficient system resources for web crawling operations
## Future Enhancements
- Integration with content planning calendar
- Automated research reports
- Competitive content gap analysis
- AI-powered content brief generation
- Customizable research workflows
---
This dashboard is designed to streamline the research process for content creators, providing powerful tools to enhance content quality, relevance, and performance. By following the suggested workflows and best practices, content creators can significantly improve their research efficiency and content outcomes.

View File

@@ -0,0 +1,98 @@
# AI Web Researcher Dashboard
## Overview
The AI Web Researcher Dashboard is a modern, intuitive interface designed specifically for content creators and digital marketing professionals. This dashboard integrates various web research tools to streamline the content research process, enhance content quality, and improve workflow efficiency.
## Features
### 1. Intuitive User Interface
- Modern, colorful, and professional design
- Easy navigation through different research tools
- Responsive layout that works on various screen sizes
### 2. Integrated Research Tools
- **Search Engine Research**: Google SERP Search and Tavily AI Search
- **Neural Search**: Metaphor Neural Search for finding conceptually similar content
- **Trend Analysis**: Google Trends Researcher for analyzing search term popularity
- **Web Crawling & Analysis**: Tools for extracting and analyzing web content
### 3. Guided Research Workflows
- Comprehensive Topic Research workflow
- Content Gap Analysis workflow
- Content Refresh & Update workflow
## Installation
1. Ensure you have Python 3.8+ installed
2. Install the required dependencies:
```
pip install -r requirements.txt
```
3. Set up the necessary API keys in your environment variables or .env file
## Usage
1. Navigate to the dashboard directory:
```
cd AI-Writer/lib/alwrity_ui/alwrity_researcher
```
2. Run the dashboard:
```
python -m streamlit run main.py
```
3. Access the dashboard in your web browser at http://localhost:8501
## Dashboard Sections
### Dashboard Home
Provides an overview of available research tools and featured workflows.
### Search Tools
Access to Google SERP Search and Tavily AI Search for comprehensive search capabilities.
### Neural Search
Use Metaphor Neural Search to find content based on conceptual similarity rather than keyword matching.
### Trend Analysis
Analyze search term popularity over time using Google Trends Researcher.
### Web Crawling
Extract structured content from websites using various web crawling tools.
### Research Workflows
Guided workflows that combine multiple tools for specific research objectives.
## Current Implementation Status
This is the initial implementation of the dashboard with placeholder functionality. The UI is fully implemented, but the actual integration with the AI web research modules will be completed in the next phase.
### What's Implemented
- Complete user interface with all sections and forms
- Mock data visualization for demonstration purposes
- Placeholder functionality for all research tools
### Next Steps
- Integrate with actual AI web research modules
- Implement data processing and visualization with real data
- Add user authentication and result saving functionality
## Technical Details
- Built with Streamlit for rapid UI development
- Modular design for easy extension and maintenance
- Responsive layout that works on desktop and mobile devices
## File Structure
- `main.py`: Entry point for the application
- `dashboard.py`: Main dashboard implementation
- `utils.py`: Utility functions for data processing and visualization
- `style.css`: Custom CSS for styling the dashboard
- `requirements.txt`: Required Python packages
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.

View File

@@ -0,0 +1,66 @@
import os
import streamlit as st
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
def get_api_key(key_name):
"""Get API key from environment variables or Streamlit secrets"""
# Try to get from environment variables first
api_key = os.getenv(key_name)
# If not found in environment, try Streamlit secrets
if not api_key and key_name in st.secrets:
api_key = st.secrets[key_name]
return api_key
def check_api_configuration():
"""Check if all required API keys are configured"""
api_status = {
"SERPER_API_KEY": bool(get_api_key("SERPER_API_KEY")),
"TAVILY_API_KEY": bool(get_api_key("TAVILY_API_KEY")),
"METAPHOR_API_KEY": bool(get_api_key("METAPHOR_API_KEY")),
"FIRECRAWL_API_KEY": bool(get_api_key("FIRECRAWL_API_KEY"))
}
return api_status
def display_api_configuration_status():
"""Display API configuration status in the sidebar with improved styling"""
api_status = check_api_configuration()
st.sidebar.markdown("<div class='api-status-container'>", unsafe_allow_html=True)
st.sidebar.markdown("<div class='api-status-title'>API Configuration Status</div>", unsafe_allow_html=True)
# Display API status with improved styling
for api_name, is_configured in api_status.items():
if is_configured:
st.sidebar.markdown(
f"<div class='api-status-item configured'>✅ {api_name}</div>",
unsafe_allow_html=True
)
else:
st.sidebar.markdown(
f"<div class='api-status-item not-configured'>❌ {api_name}</div>",
unsafe_allow_html=True
)
# Display instructions if any API key is missing with improved styling
if not all(api_status.values()):
with st.sidebar.expander("How to configure API keys"):
st.markdown("""
<div style="background-color: #f8fafc; padding: 12px; border-radius: 6px; border-left: 4px solid #3b82f6;">
<p style="margin-bottom: 10px; font-weight: 500;">To configure missing API keys, create a <code>.env</code> file in the project root with the following format:</p>
<pre style="background-color: #f1f5f9; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 0.9em;">
SERPER_API_KEY=your_serper_api_key
TAVILY_API_KEY=your_tavily_api_key
METAPHOR_API_KEY=your_metaphor_api_key
FIRECRAWL_API_KEY=your_firecrawl_api_key
</pre>
<p style="margin-top: 10px;">Alternatively, you can set these as environment variables in your system.</p>
</div>
""", unsafe_allow_html=True)
st.sidebar.markdown("</div>", unsafe_allow_html=True)

View File

@@ -0,0 +1,729 @@
import streamlit as st
import sys
import os
from pathlib import Path
# Add parent directory to path to import modules
sys.path.append(str(Path(__file__).parent.parent.parent))
# Import utils module
from utils import (
load_css,
display_google_serp_results,
display_tavily_results,
display_metaphor_results,
display_google_trends_results,
display_crawler_results,
display_analyzer_results
)
# Configure Streamlit page settings
st.set_page_config(
page_title="AI Web Researcher Dashboard",
page_icon="🔍",
layout="wide",
initial_sidebar_state="expanded"
)
# Apply custom CSS immediately
st.markdown("""
<style>
.main-header {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.category-header {
font-size: 20px;
font-weight: bold;
margin: 15px 0;
color: #0066cc;
}
/* Make the dashboard responsive */
.stTabs [data-baseweb="tab-list"] {
gap: 2px;
flex-wrap: wrap;
}
.stTabs [data-baseweb="tab"] {
white-space: pre-wrap;
min-width: fit-content;
font-size: 14px;
padding: 8px 16px;
}
/* Adjust container width */
.block-container {
max-width: 95% !important;
padding: 1rem 1rem 10rem !important;
}
/* Additional styling for better visibility */
.stApp {
background-color: #f8f9fa;
}
.stTabs [data-baseweb="tab"] [data-testid="stMarkdownContainer"] p {
font-size: 14px !important;
margin-bottom: 0px !important;
}
.stTabs [data-baseweb="tab-list"] button {
background-color: #ffffff;
border: 1px solid #e9ecef;
border-radius: 4px;
margin: 2px;
}
.stTabs [data-baseweb="tab-list"] button[aria-selected="true"] {
background-color: #e7f1ff;
border-color: #b8daff;
}
</style>
""", unsafe_allow_html=True)
# CSS is now loaded at the top of the file
# Initialize session state variables
def init_session_state():
if 'current_section' not in st.session_state:
st.session_state['current_section'] = 'Dashboard Home'
# Initialize session state at startup
init_session_state()
def main():
# Initialize session state before accessing it
init_session_state()
# Main navigation header
st.markdown('<div class="main-nav-header">AI Web Researcher</div>', unsafe_allow_html=True)
# Create tabs for navigation
selected_section = st.tabs([
"🏠 Dashboard Home",
"🔍 Search Tools",
"🧠 Neural Search",
"📈 Trend Analysis",
"🕸️ Web Crawling",
"📚 Academic Research",
"📋 Research Workflows"
])
# Display appropriate section based on selected tab
with selected_section[0]:
display_home()
with selected_section[1]:
display_search_tools()
with selected_section[2]:
display_neural_search()
with selected_section[3]:
display_trend_analysis()
with selected_section[4]:
display_web_crawling()
with selected_section[5]:
display_academic_research()
with selected_section[6]:
display_research_workflows()
# Ensure CSS is consistently applied
load_css()
def display_home():
# Main header with improved styling
st.markdown('<div class="main-header">AI Web Researcher Dashboard</div>', unsafe_allow_html=True)
# Introduction with better formatting
st.markdown("""
<div class="intro-container">
<p class="intro-text">Welcome to the <span class="intro-highlight">AI Web Researcher Dashboard</span>, a comprehensive suite of research tools designed
specifically for content creators and digital marketing professionals. This dashboard integrates
various web research modules to streamline your content research process, enhance content quality,
and improve workflow efficiency.</p>
</div>
""", unsafe_allow_html=True)
# Quick access to tool categories with improved header
st.markdown('<div class="category-header">Research Tool Categories</div>', unsafe_allow_html=True)
# Use 2 columns with equal width for better layout
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("""
<div class="tool-card">
<div class="tool-title">🔍 Search Engine Research</div>
<div class="tool-description">Powerful search tools to understand search intent, identify content gaps, and discover high-performing content in your niche</div>
<div class="tool-features"><b>Includes:</b> Google SERP Search • Tavily AI Search</div>
<div class="tool-features"><b>Use cases:</b> Keyword research, competitor analysis, content planning, identifying user questions</div>
<div style="margin-top: 12px;">
<span class="tool-badge">SERP Analysis</span>
<span class="tool-badge ai-powered">AI-Powered</span>
</div>
</div>
<div class="tool-card">
<div class="tool-title">🧠 Neural Search</div>
<div class="tool-description">Advanced semantic search technology that understands concepts rather than just keywords to find truly relevant content</div>
<div class="tool-features"><b>Includes:</b> Metaphor Neural Search</div>
<div class="tool-features"><b>Use cases:</b> Finding conceptually similar articles, discovering unique content angles, competitive research</div>
<div style="margin-top: 12px;">
<span class="tool-badge semantic">Semantic</span>
<span class="tool-badge deep-learning">Deep Learning</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="tool-card">
<div class="tool-title">📈 Trend Analysis</div>
<div class="tool-description">Comprehensive trend analysis tools that track search term popularity over time to identify seasonal patterns and emerging topics</div>
<div class="tool-features"><b>Includes:</b> Google Trends Researcher</div>
<div class="tool-features"><b>Use cases:</b> Seasonal content planning, topic validation, identifying rising trends, content calendar optimization</div>
<div style="margin-top: 12px;">
<span class="tool-badge time-series">Time Series</span>
<span class="tool-badge forecasting">Forecasting</span>
</div>
</div>
<div class="tool-card">
<div class="tool-title">🕸️ Web Crawling & Analysis</div>
<div class="tool-description">Powerful web extraction tools that gather and structure content from websites for in-depth analysis and insights</div>
<div class="tool-features"><b>Includes:</b> Async Web Crawler • Firecrawl Web Crawler • Website Analyzer</div>
<div class="tool-features"><b>Use cases:</b> Content auditing, competitor analysis, data extraction, market research, content aggregation</div>
<div style="margin-top: 12px;">
<span class="tool-badge content-extraction">Content Extraction</span>
<span class="tool-badge data-analysis">Data Analysis</span>
</div>
</div>
""", unsafe_allow_html=True)
# Featured workflow with improved header
st.markdown('<div class="category-header">Featured Research Workflow</div>', unsafe_allow_html=True)
st.markdown("""
<div class="workflow-card">
<div class="workflow-title">Comprehensive Topic Research Workflow</div>
<p style="color: #4b5563; margin-bottom: 1.25rem;">Follow this proven research workflow to develop comprehensive, data-driven content that resonates with your audience and performs well in search.</p>
<div class="workflow-step-container">
<div class="workflow-step-number">1</div>
<div class="workflow-step-content">
<div class="step-title">Initial Exploration</div>
<div class="workflow-step-description">Use Google SERP Search to understand the search landscape, identify user intent, and discover what content currently ranks well. Analyze People Also Ask questions to identify key user concerns.</div>
</div>
</div>
<div class="workflow-step-container">
<div class="workflow-step-number">2</div>
<div class="workflow-step-content">
<div class="step-title">In-depth Research</div>
<div class="workflow-step-description">Use Tavily AI Search for deeper research on identified subtopics. The AI-powered search provides more contextual information and helps uncover expert insights that might be missed in traditional searches.</div>
</div>
</div>
<div class="workflow-step-container">
<div class="workflow-step-number">3</div>
<div class="workflow-step-content">
<div class="step-title">Competitive Analysis</div>
<div class="workflow-step-description">Use Metaphor Neural Search to find conceptually similar content from competitors. Analyze their approach, identify content gaps, and discover unique angles that differentiate your content.</div>
</div>
</div>
<div class="workflow-step-container">
<div class="workflow-step-number">4</div>
<div class="workflow-step-content">
<div class="step-title">Trend Validation</div>
<div class="workflow-step-description">Use Google Trends Researcher to verify topic popularity, identify seasonal patterns, and discover related trending topics. This ensures your content is timely and aligned with current audience interests.</div>
</div>
</div>
<div class="workflow-step-container">
<div class="workflow-step-number">5</div>
<div class="workflow-step-content">
<div class="step-title">Content Extraction</div>
<div class="workflow-step-description">Use Web Crawling tools to extract specific content from top-performing pages for detailed analysis. This helps identify content structure, depth, and formatting approaches that resonate with your audience.</div>
</div>
</div>
</div>
""", unsafe_allow_html=True)
def display_search_tools():
st.markdown('<div class="main-header">Search Engine Research Tools</div>', unsafe_allow_html=True)
# Google SERP Search
st.markdown('<div class="category-header">Google SERP Search</div>', unsafe_allow_html=True)
st.markdown("""
<div class="tool-card">
<div class="tool-title">Google SERP Search</div>
<div class="tool-description">Retrieves organic search results, People Also Ask questions, and related searches</div>
<div class="tool-features">
<b>Best for:</b> Initial topic research, understanding search intent, and identifying content gaps<br>
<b>Key features:</b> Comprehensive search results with structured data extraction
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Google SERP Search
with st.form("google_serp_search_form"):
search_query = st.text_input("Enter your search query")
col1, col2 = st.columns(2)
with col1:
num_results = st.slider("Number of results", 5, 30, 10)
with col2:
include_paa = st.checkbox("Include People Also Ask", value=True)
submitted = st.form_submit_button("Search")
if submitted and search_query:
try:
with st.spinner("Searching Google SERP..."):
# Import the actual module
from ai_web_researcher.google_serp_search import google_search
# Call the actual implementation
results = google_search(search_query)
# Display the results
if results:
display_google_serp_results(results)
else:
st.error("No results found. Please try a different query.")
except Exception as e:
st.error(f"Error performing Google SERP search: {str(e)}")
st.info("Please check your API configuration in the .env file.")
st.info("Required API: SERPER_API_KEY for Google SERP Search")
display_google_serp_results(None)
# Tavily AI Search
st.markdown('<div class="category-header">Tavily AI Search</div>', unsafe_allow_html=True)
st.markdown("""
<div class="tool-card">
<div class="tool-title">Tavily AI Search</div>
<div class="tool-description">Advanced AI-powered search with semantic understanding</div>
<div class="tool-features">
<b>Best for:</b> In-depth research requiring contextual understanding<br>
<b>Key features:</b> Provides direct answers to questions and follow-up question suggestions
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Tavily AI Search
with st.form("tavily_ai_search_form"):
search_query = st.text_input("Enter your search query or question")
search_depth = st.select_slider("Search depth", options=["basic", "medium", "deep"], value="medium")
submitted = st.form_submit_button("Search with Tavily AI")
if submitted and search_query:
try:
with st.spinner("Searching with Tavily AI..."):
# Import the actual module
from ai_web_researcher.tavily_ai_search import do_tavily_ai_search
# Call the actual implementation
results = do_tavily_ai_search(search_query, search_depth=search_depth)
# Display the results
if results:
display_tavily_results(results)
else:
st.error("No results found. Please try a different query.")
except Exception as e:
st.error(f"Error performing Tavily AI search: {str(e)}")
st.info("Please check your API configuration in the .env file.")
st.info("Required API: TAVILY_API_KEY for Tavily AI Search")
display_tavily_results(None)
def display_neural_search():
st.markdown('<div class="main-header">Neural Search Tools</div>', unsafe_allow_html=True)
# Metaphor Neural Search
st.markdown('<div class="category-header">Metaphor Neural Search</div>', unsafe_allow_html=True)
st.markdown("""
<div class="tool-card">
<div class="tool-title">Metaphor Neural Search</div>
<div class="tool-description">Semantic search technology for finding related content</div>
<div class="tool-features">
<b>Best for:</b> Discovering content based on conceptual similarity rather than keyword matching<br>
<b>Key features:</b> Find similar articles, competitor analysis, and content inspiration
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Metaphor Neural Search
with st.form("metaphor_search_form"):
st.markdown("**Search by keyword or find similar content to a URL**")
search_type = st.radio("Search type", ["Keyword Search", "Similar Content Search"])
if search_type == "Keyword Search":
search_query = st.text_input("Enter your search query")
url_input = ""
else:
search_query = ""
url_input = st.text_input("Enter a URL to find similar content")
num_results = st.slider("Number of results", 3, 20, 5)
submitted = st.form_submit_button("Search with Metaphor")
if submitted and (search_query or url_input):
try:
with st.spinner("Searching with Metaphor Neural Search..."):
# Import the actual module
from ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles, metaphor_find_similar
# Call the actual implementation
if search_query:
results = metaphor_search_articles(search_query, num_results=num_results)
else:
results = metaphor_find_similar(url_input, num_results=num_results)
# Display the results
if results:
display_metaphor_results(results)
else:
st.error("No results found. Please try a different query.")
except Exception as e:
st.error(f"Error performing Metaphor Neural search: {str(e)}")
st.info("Please check your API configuration in the .env file.")
st.info("Required API: METAPHOR_API_KEY for Metaphor Neural Search")
display_metaphor_results(None)
def display_trend_analysis():
st.markdown('<div class="main-header">Trend Analysis Tools</div>', unsafe_allow_html=True)
# Google Trends Researcher
st.markdown('<div class="category-header">Google Trends Researcher</div>', unsafe_allow_html=True)
st.markdown("""
<div class="tool-card">
<div class="tool-title">Google Trends Researcher</div>
<div class="tool-description">Analyze search term popularity and related queries</div>
<div class="tool-features">
<b>Best for:</b> Content planning, trend forecasting, and seasonal content optimization<br>
<b>Key features:</b> Interest over time charts, related queries, and regional interest data
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Google Trends Researcher
with st.form("google_trends_form"):
search_terms = st.text_area("Enter search terms (one per line)")
col1, col2 = st.columns(2)
with col1:
time_frame = st.select_slider("Time range", options=["past_hour", "past_day", "past_week", "past_month", "past_90_days", "past_12_months", "past_5_years"], value="past_12_months")
with col2:
geo = st.text_input("Geographic region (ISO country code, e.g., 'US')", value="US")
submitted = st.form_submit_button("Analyze Trends")
if submitted and search_terms:
try:
with st.spinner("Analyzing Google Trends..."):
# Import the actual module
from ai_web_researcher.google_trends_researcher import do_google_trends_analysis
# Call the actual implementation
search_terms_list = [term.strip() for term in search_terms.split('\n') if term.strip()]
results = do_google_trends_analysis(search_terms_list, time_frame=time_frame, geo=geo)
# Display the results
if results:
display_google_trends_results(results)
else:
st.error("No trend data found. Please try different search terms.")
except Exception as e:
st.error(f"Error analyzing Google Trends: {str(e)}")
st.info("Google Trends analysis doesn't require an API key, but there might be rate limiting or network issues.")
display_google_trends_results(None)
def display_web_crawling():
st.markdown('<div class="main-header">Web Crawling & Analysis Tools</div>', unsafe_allow_html=True)
# Create tabs for different crawling tools
tab1, tab2, tab3 = st.tabs(["Firecrawl Web Crawler", "Website Analyzer", "Async Web Crawler"])
with tab1:
st.markdown("""
<div class="tool-card">
<div class="tool-title">Firecrawl Web Crawler</div>
<div class="tool-description">Extract structured content from websites</div>
<div class="tool-features">
<b>Best for:</b> Content extraction, competitor analysis, and website auditing<br>
<b>Key features:</b> Extracts titles, descriptions, headings, and content from web pages
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Firecrawl Web Crawler
with st.form("firecrawl_form"):
website_url = st.text_input("Enter website URL")
depth = st.slider("Crawl depth", 1, 5, 1)
max_pages = st.slider("Maximum pages", 1, 50, 10)
submitted = st.form_submit_button("Crawl Website")
if submitted and website_url:
try:
with st.spinner("Crawling website..."):
# Import the actual module
from ai_web_researcher.firecrawl_web_crawler import scrape_website
# Call the actual implementation
results = scrape_website(website_url, depth=depth, max_pages=max_pages)
# Display the results
if results:
display_crawler_results(results)
else:
st.error("No crawler results found. Please try a different URL.")
except Exception as e:
st.error(f"Error crawling website: {str(e)}")
st.info("Please check your API configuration in the .env file.")
st.info("Required API: FIRECRAWL_API_KEY for Web Crawler")
display_crawler_results(None)
with tab2:
st.markdown("""
<div class="tool-card">
<div class="tool-title">Website Analyzer</div>
<div class="tool-description">Analyze website content and structure</div>
<div class="tool-features">
<b>Best for:</b> Content analysis, SEO auditing, and competitor research<br>
<b>Key features:</b> Content analysis, keyword extraction, and readability metrics
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Website Analyzer
with st.form("website_analyzer_form"):
website_url = st.text_input("Enter website URL")
analyze_type = st.selectbox("Analysis type", ["Basic Analysis", "SEO Analysis", "Content Analysis", "Competitor Analysis"])
submitted = st.form_submit_button("Analyze Website")
if submitted and website_url:
st.info("Website Analyzer is coming soon. This feature is under development.")
with tab3:
st.markdown("""
<div class="tool-card">
<div class="tool-title">Async Web Crawler</div>
<div class="tool-description">High-performance asynchronous web crawler</div>
<div class="tool-features">
<b>Best for:</b> Large-scale crawling, data extraction, and content aggregation<br>
<b>Key features:</b> Fast, efficient crawling with customizable extraction rules
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Async Web Crawler
with st.form("async_crawler_form"):
website_url = st.text_input("Enter website URL")
max_urls = st.slider("Maximum URLs to crawl", 10, 100, 30)
submitted = st.form_submit_button("Start Crawling")
if submitted and website_url:
st.info("Async Web Crawler is coming soon. This feature is under development.")
def display_academic_research():
st.markdown('<div class="main-header">Academic Research Tools</div>', unsafe_allow_html=True)
# ArXiv Search Section
st.markdown('<div class="category-header">ArXiv Scholarly Search</div>', unsafe_allow_html=True)
# Search Parameters
search_col1, search_col2 = st.columns([2, 1])
with search_col1:
search_query = st.text_input("🔍 Enter research topic or keywords", key="arxiv_search")
with search_col2:
max_results = st.number_input("Maximum Results", min_value=1, max_value=50, value=10)
# Search Button
if st.button("🔎 Search ArXiv", key="arxiv_search_button"):
if search_query:
with st.spinner("Searching ArXiv database..."):
try:
# Import arxiv search function
from ai_web_researcher.arxiv_schlorly_research import fetch_arxiv_data, create_dataframe
# Fetch results
results = fetch_arxiv_data(search_query, max_results)
if results:
# Create DataFrame
df = create_dataframe(results, ["Title", "Published Date", "ArXiv ID", "Summary", "PDF URL"])
# Display results in an expander
with st.expander("📚 Search Results", expanded=True):
# Display each paper with options to view abstract and download
for idx, row in df.iterrows():
st.markdown(f"### {row['Title']}")
st.markdown(f"*Published: {row['Published Date']}*")
# Create columns for buttons
btn_col1, btn_col2, btn_col3 = st.columns([1, 1, 1])
with btn_col1:
if st.button(f"📄 View Abstract #{idx}"):
st.markdown(f"**Abstract:**\n{row['Summary']}")
with btn_col2:
st.markdown(f"[📥 Download PDF]({row['PDF URL']})")
if st.button(f"📝 Summarize #{idx}"):
with st.spinner("Generating summary..."):
try:
from ai_web_researcher.gpt_summarize_web_content import summarize_web_content
summary = summarize_web_content(row['PDF URL'])
if summary:
st.markdown("### GPT Summary")
st.markdown(summary)
# Add export option for the summary
st.download_button(
label="📥 Export Summary",
data=summary,
file_name=f"summary_{row['ArXiv ID']}.txt",
mime="text/plain"
)
except Exception as e:
st.error(f"Error generating summary: {str(e)}")
with btn_col3:
if st.button(f"🔍 Related Web Content #{idx}"):
# Use Google SERP to find related content
from ai_web_researcher.google_serp_search import google_search
web_results = google_search(row['Title'])
if web_results:
st.markdown("### Related Web Content")
for result in web_results['organic'][:3]:
st.markdown(f"- [{result['title']}]({result['link']})\n {result['snippet']}")
st.markdown("---")
else:
st.warning("No results found. Try modifying your search terms.")
except Exception as e:
st.error(f"An error occurred while searching: {str(e)}")
else:
st.warning("Please enter a search query.")
# Research Notes Section
st.markdown('<div class="category-header">Research Notes</div>', unsafe_allow_html=True)
# Initialize session state for notes if not exists
if 'research_notes' not in st.session_state:
st.session_state.research_notes = {}
notes_col1, notes_col2 = st.columns([2, 1])
with notes_col1:
paper_id = st.text_input("ArXiv ID or Paper Title", key="notes_paper_id")
notes_content = st.text_area("Research Notes", height=200, key="notes_content")
if st.button("Save Notes"):
if paper_id:
st.session_state.research_notes[paper_id] = notes_content
st.success("Notes saved successfully!")
else:
st.warning("Please enter a paper identifier.")
with notes_col2:
st.markdown("### Saved Notes")
for paper_id, notes in st.session_state.research_notes.items():
with st.expander(f"📝 {paper_id}"):
st.text_area("Saved Notes", value=notes, height=150, key=f"saved_{paper_id}", disabled=True)
if st.button("Export Notes", key=f"export_{paper_id}"):
notes_export = f"Research Notes for {paper_id}\n\n{notes}"
st.download_button(
label="📥 Download Notes",
data=notes_export,
file_name=f"research_notes_{paper_id}.txt",
mime="text/plain"
)
# Citation Management Section
st.markdown('<div class="category-header">Citation Management</div>', unsafe_allow_html=True)
# BibTeX Export
st.markdown("### Export Citations")
arxiv_id = st.text_input("Enter ArXiv ID for citation export")
if arxiv_id and st.button("Generate BibTeX"):
try:
from ai_web_researcher.arxiv_schlorly_research import arxiv_bibtex
bibtex = arxiv_bibtex(arxiv_id)
if bibtex:
st.code(bibtex, language="bibtex")
st.download_button(
label="📥 Download BibTeX",
data=bibtex,
file_name=f"citation_{arxiv_id}.bib",
mime="text/plain"
)
except Exception as e:
st.error(f"Error generating citation: {str(e)}")
def display_research_workflows():
st.markdown('<div class="main-header">Research Workflows</div>', unsafe_allow_html=True)
st.markdown("""
<div class="workflow-description">
Research workflows combine multiple research tools to provide comprehensive insights for specific content creation tasks.
Select a workflow to get started.
</div>
""", unsafe_allow_html=True)
# Create tabs for different workflows
tab1, tab2, tab3 = st.tabs(["Topic Research", "Competitor Analysis", "Trend Discovery"])
with tab1:
st.markdown("""
<div class="workflow-card">
<div class="workflow-title">Comprehensive Topic Research</div>
<div class="workflow-description">
This workflow helps you thoroughly research a topic for content creation by combining search results,
semantic understanding, and trend analysis.
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Topic Research workflow
with st.form("topic_research_form"):
topic = st.text_input("Enter your topic")
include_trends = st.checkbox("Include trend analysis", value=True)
include_competitors = st.checkbox("Include competitor analysis", value=True)
submitted = st.form_submit_button("Start Research Workflow")
if submitted and topic:
st.info("Research workflows are coming soon. This feature is under development.")
with tab2:
st.markdown("""
<div class="workflow-card">
<div class="workflow-title">Competitor Content Analysis</div>
<div class="workflow-description">
This workflow analyzes your competitors' content to identify gaps and opportunities for your own content strategy.
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Competitor Analysis workflow
with st.form("competitor_analysis_form"):
competitor_urls = st.text_area("Enter competitor URLs (one per line)")
topic_focus = st.text_input("Topic focus (optional)")
submitted = st.form_submit_button("Start Competitor Analysis")
if submitted and competitor_urls:
st.info("Research workflows are coming soon. This feature is under development.")
with tab3:
st.markdown("""
<div class="workflow-card">
<div class="workflow-title">Trend Discovery & Content Planning</div>
<div class="workflow-description">
This workflow identifies trending topics in your niche and helps you plan content around them.
</div>
</div>
""", unsafe_allow_html=True)
# Input form for Trend Discovery workflow
with st.form("trend_discovery_form"):
niche = st.text_input("Enter your niche or industry")
time_period = st.select_slider("Time period", options=["past_week", "past_month", "past_90_days", "past_12_months"], value="past_month")
submitted = st.form_submit_button("Discover Trends")
if submitted and niche:
st.info("Research workflows are coming soon. This feature is under development.")

View File

@@ -0,0 +1,14 @@
import streamlit as st
import sys
import os
from pathlib import Path
# Add the current directory to the path
sys.path.append(str(Path(__file__).parent))
# Import the dashboard module
from dashboard import main
# Run the dashboard
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,517 @@
/* Main Dashboard Styles */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: #f0f4f8;
color: #1f2937;
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.main-header {
font-size: 2.2rem;
background: linear-gradient(120deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
margin: 1.5rem 0;
font-weight: 700;
letter-spacing: -0.025em;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.category-header {
font-size: 1.4rem;
background: linear-gradient(90deg, #2563eb 0%, #4f46e5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-top: 1.5rem;
margin-bottom: 1rem;
font-weight: 600;
padding-left: 0.5rem;
border-left: 4px solid;
border-image: linear-gradient(to bottom, #3b82f6, #4f46e5) 1;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
/* Glassomorphic Tool Card Styles */
.tool-card {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.75rem;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
border: 1px solid rgba(255, 255, 255, 0.18);
margin-bottom: 1.5rem;
border-left: 4px solid;
border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1;
transition: all 0.3s ease;
}
.tool-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(31, 38, 135, 0.25);
border-image: linear-gradient(to bottom, rgba(59, 130, 246, 1), rgba(79, 70, 229, 1)) 1;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.9) 0%, rgba(240, 249, 255, 0.8) 100%);
}
.tool-title {
font-size: 1.3rem;
font-weight: 600;
background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.tool-description {
color: #374151;
margin-bottom: 1rem;
line-height: 1.6;
font-size: 1rem;
}
.tool-features {
font-size: 0.95rem;
color: #4b5563;
margin-bottom: 0.75rem;
line-height: 1.6;
background-color: rgba(243, 244, 246, 0.6);
padding: 0.85rem;
border-radius: 8px;
border: 1px solid rgba(229, 231, 235, 0.5);
}
/* Tool Badge Styles */
.tool-badge {
display: inline-block;
padding: 0.35rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
margin-right: 0.5rem;
background-color: rgba(243, 244, 246, 0.8);
color: #4b5563;
border: 1px solid rgba(209, 213, 219, 0.5);
backdrop-filter: blur(5px);
}
.tool-badge.ai-powered {
background-color: rgba(79, 70, 229, 0.15);
color: #4338ca;
border-color: rgba(79, 70, 229, 0.3);
}
.tool-badge.semantic {
background-color: rgba(16, 185, 129, 0.15);
color: #065f46;
border-color: rgba(16, 185, 129, 0.3);
}
.tool-badge.deep-learning {
background-color: rgba(245, 158, 11, 0.15);
color: #92400e;
border-color: rgba(245, 158, 11, 0.3);
}
.tool-badge.time-series {
background-color: rgba(6, 182, 212, 0.15);
color: #0e7490;
border-color: rgba(6, 182, 212, 0.3);
}
.tool-badge.forecasting {
background-color: rgba(168, 85, 247, 0.15);
color: #6d28d9;
border-color: rgba(168, 85, 247, 0.3);
}
.tool-badge.content-extraction {
background-color: rgba(236, 72, 153, 0.15);
color: #be185d;
border-color: rgba(236, 72, 153, 0.3);
}
.tool-badge.data-analysis {
background-color: rgba(14, 165, 233, 0.15);
color: #0369a1;
border-color: rgba(14, 165, 233, 0.3);
}
/* Glassomorphic Workflow Card Styles */
.workflow-card {
background: linear-gradient(135deg, rgba(239, 246, 255, 0.8) 0%, rgba(219, 234, 254, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.75rem;
margin-bottom: 1.5rem;
border-left: 4px solid;
border-image: linear-gradient(to bottom, rgba(37, 99, 235, 0.8), rgba(79, 70, 229, 0.8)) 1;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.workflow-title {
font-size: 1.3rem;
font-weight: 600;
background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1.25rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(191, 219, 254, 0.5);
}
.workflow-step-container {
display: flex;
margin-bottom: 1.25rem;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(240, 249, 255, 0.6) 100%);
border-radius: 10px;
padding: 1rem;
box-shadow: 0 4px 6px rgba(31, 38, 135, 0.1);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.workflow-step-number {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.9) 0%, rgba(79, 70, 229, 0.9) 100%);
color: white;
border-radius: 50%;
font-weight: 600;
margin-right: 1rem;
box-shadow: 0 4px 6px rgba(37, 99, 235, 0.3);
}
.workflow-step-content {
flex: 1;
}
.step-title {
font-weight: 600;
background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
font-size: 1.05rem;
}
.workflow-step-description {
color: #4b5563;
line-height: 1.5;
font-size: 0.95rem;
}
/* Navigation Styles */
.nav-header {
font-size: 2rem;
background: linear-gradient(120deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
margin: 1.5rem 0;
font-weight: 700;
letter-spacing: -0.025em;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.nav-container {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.75rem;
margin-bottom: 1.5rem;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.nav-button {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1rem;
border: 1px solid rgba(255, 255, 255, 0.18);
border-left: 4px solid;
border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1;
transition: all 0.3s ease;
width: 100%;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
}
.nav-button:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(31, 38, 135, 0.25);
border-image: linear-gradient(to bottom, rgba(59, 130, 246, 1), rgba(79, 70, 229, 1)) 1;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.9) 0%, rgba(240, 249, 255, 0.8) 100%);
}
.nav-button.selected {
background: linear-gradient(120deg, rgba(239, 246, 255, 0.9) 0%, rgba(219, 234, 254, 0.8) 100%);
border-image: linear-gradient(to bottom, #3b82f6, #4f46e5) 1;
box-shadow: 0 8px 20px rgba(31, 38, 135, 0.2);
}
.nav-icon {
font-size: 1.8rem;
margin-bottom: 0.75rem;
background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-title {
font-size: 1.1rem;
font-weight: 600;
background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
}
[data-testid="stSidebar"] {
background-color: rgba(248, 250, 252, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-right: 1px solid rgba(229, 231, 235, 0.5);
}
[data-testid="stSidebarNav"] {
background-color: transparent;
}
.st-emotion-cache-16txtl3 {
padding-top: 2rem;
}
.sidebar-header {
font-size: 1.3rem;
font-weight: 700;
color: #1e3a8a;
margin: 1rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
text-align: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Button Styles */
.stButton>button {
background: linear-gradient(135deg, #3b82f6 0%, #4f46e5 100%);
color: white;
width: 100%;
font-weight: 500;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.18);
padding: 0.6rem 1.2rem;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
box-shadow: 0 4px 6px rgba(31, 38, 135, 0.15);
}
.stButton>button:hover {
background: linear-gradient(135deg, #2563eb 0%, #4338ca 100%);
box-shadow: 0 8px 15px rgba(31, 38, 135, 0.2);
transform: translateY(-2px);
}
/* Glassomorphic Results Display Styles */
.results-container {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.75rem;
margin-top: 1.25rem;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.result-item {
padding: 1.25rem;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
margin-bottom: 1.25rem;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(248, 250, 252, 0.6) 100%);
border-radius: 8px;
transition: all 0.2s ease;
}
.result-item:hover {
background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 6px rgba(31, 38, 135, 0.1);
}
.result-title {
font-size: 1.15rem;
font-weight: 600;
color: #1e3a8a;
margin-bottom: 0.6rem;
}
.result-snippet {
color: #4b5563;
font-size: 0.95rem;
line-height: 1.6;
}
.result-url {
color: #059669;
font-size: 0.85rem;
margin-top: 0.6rem;
word-break: break-all;
}
/* Form Styles */
.stTextInput>div>div>input {
border-radius: 8px;
border: 1px solid rgba(209, 213, 219, 0.5);
background-color: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.stTextInput>div>div>input:focus {
border-color: rgba(59, 130, 246, 0.7);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
background-color: rgba(255, 255, 255, 0.9);
}
/* Dashboard Home Styles */
.intro-container {
background: linear-gradient(135deg, rgba(239, 246, 255, 0.8) 0%, rgba(224, 242, 254, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 1.75rem;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
margin-bottom: 1.5rem;
border-left: 4px solid rgba(59, 130, 246, 0.7);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.intro-text {
color: #374151;
line-height: 1.7;
font-size: 1.05rem;
margin: 0;
}
.intro-highlight {
color: #1e3a8a;
font-weight: 600;
}
/* Chart Container Styles */
.chart-container {
background-color: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1.5rem;
margin: 1.25rem 0;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.chart-title {
font-size: 1.1rem;
font-weight: 600;
color: #1e3a8a;
margin-bottom: 1rem;
text-align: center;
}
/* Tab Styles */
.stTabs [data-baseweb="tab-list"] {
gap: 12px;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
}
.stTabs [data-baseweb="tab"] {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(240, 249, 255, 0.6) 100%);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border-radius: 10px;
padding: 0.75rem 1.25rem;
border: 1px solid rgba(255, 255, 255, 0.18);
border-left: 3px solid;
border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1;
transition: all 0.3s ease;
font-weight: 500;
color: #1e3a8a;
}
.stTabs [data-baseweb="tab"]:hover {
transform: translateY(-2px);
box-shadow: 0 8px 15px rgba(31, 38, 135, 0.15);
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
}
.stTabs [aria-selected="true"] {
background: linear-gradient(135deg, #3b82f6 0%, #4f46e5 100%) !important;
color: white !important;
border-image: none !important;
border-left: 3px solid #3b82f6 !important;
box-shadow: 0 8px 15px rgba(59, 130, 246, 0.25) !important;
}
/* Additional Styles for Tabs Content */
.stTabs [data-baseweb="tab-panel"] {
background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 0 12px 12px 12px;
padding: 1.75rem;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
margin-top: -1px;
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.tool-card, .workflow-card, .intro-container, .results-container {
padding: 1.25rem;
}
.main-header {
font-size: 1.8rem;
}
.category-header {
font-size: 1.2rem;
}
.tool-title, .workflow-title {
font-size: 1.15rem;
}
}

View File

@@ -0,0 +1,380 @@
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import sys
import os
from pathlib import Path
# Add parent directory to path to import modules
sys.path.append(str(Path(__file__).parent.parent.parent))
# Import research modules (placeholder imports for now)
try:
from ai_web_researcher import (
google_serp_search,
tavily_ai_search,
metaphor_basic_neural_web_search,
google_trends_researcher,
firecrawl_web_crawler
)
except ImportError:
# For development/testing without actual modules
pass
def load_css():
"""Load custom CSS"""
css_file = Path(__file__).parent / "style.css"
with open(css_file) as f:
css_content = f.read()
# Use session state to track if CSS has been loaded
if 'css_loaded' not in st.session_state:
st.session_state['css_loaded'] = False
# Always apply CSS on each page load to ensure styles persist during navigation
st.markdown(f"<style>{css_content}</style>", unsafe_allow_html=True)
st.session_state['css_loaded'] = True
def display_google_serp_results(results):
"""Display Google SERP search results"""
# Check if results are available
if not results:
st.warning("No search results available. Please try a different query or check your API configuration.")
return
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display organic results
st.markdown('<div class="category-header">Search Results</div>', unsafe_allow_html=True)
# Display actual organic results
organic_results = results.get('organic', [])
if organic_results:
for result in organic_results:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{result.get('title', 'No Title')}</div>
<div class="result-url">{result.get('link', '#')}</div>
<div class="result-snippet">{result.get('snippet', 'No description available.')}</div>
</div>
""", unsafe_allow_html=True)
else:
st.info("No organic search results found.")
# Display People Also Ask
paa_results = results.get('peopleAlsoAsk', [])
if paa_results:
st.markdown('<div class="category-header">People Also Ask</div>', unsafe_allow_html=True)
for question in paa_results:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{question.get('question', 'No Question')}</div>
<div class="result-snippet">{question.get('snippet', 'No answer available.')}</div>
</div>
""", unsafe_allow_html=True)
# Display Related Searches if available
related_searches = results.get('relatedSearches', [])
if related_searches:
st.markdown('<div class="category-header">Related Searches</div>', unsafe_allow_html=True)
for search in related_searches:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{search}</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def display_tavily_results(results):
"""Display Tavily AI search results"""
# Check if results are available
if not results:
st.warning("No Tavily search results available. Please try a different query or check your API configuration.")
return
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display answer if available
answer = results.get('answer', '')
if answer:
st.markdown('<div class="category-header">Answer</div>', unsafe_allow_html=True)
st.markdown(f"""
<div class="result-item">
<div class="result-snippet">{answer}</div>
</div>
""", unsafe_allow_html=True)
# Display search results
search_results = results.get('results', [])
if search_results:
st.markdown('<div class="category-header">Search Results</div>', unsafe_allow_html=True)
for result in search_results:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{result.get('title', 'No Title')}</div>
<div class="result-url">{result.get('url', '#')}</div>
<div class="result-snippet">{result.get('content', 'No content available.')}</div>
</div>
""", unsafe_allow_html=True)
else:
st.info("No search results found.")
# Display follow-up questions if available
follow_up_questions = results.get('follow_up_questions', [])
if follow_up_questions:
st.markdown('<div class="category-header">Follow-up Questions</div>', unsafe_allow_html=True)
for question in follow_up_questions:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{question}</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def display_metaphor_results(results):
"""Display Metaphor Neural Search results"""
# Check if results are available
if not results:
st.warning("No Metaphor search results available. Please try a different query or check your API configuration.")
return
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display search results
st.markdown('<div class="category-header">Similar Content</div>', unsafe_allow_html=True)
# Display actual results
documents = results.get('documents', [])
if documents:
for doc in documents:
title = doc.get('title', 'No Title')
url = doc.get('url', '#')
extract = doc.get('extract', 'No content available.')
st.markdown(f"""
<div class="result-item">
<div class="result-title">{title}</div>
<div class="result-url">{url}</div>
<div class="result-snippet">{extract}</div>
</div>
""", unsafe_allow_html=True)
else:
st.info("No similar content found.")
# Display summary if available
summary = results.get('summary', '')
if summary:
st.markdown('<div class="category-header">Content Summary</div>', unsafe_allow_html=True)
st.markdown(f"""
<div class="result-item">
<div class="result-snippet">{summary}</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def display_google_trends_results(results):
"""Display Google Trends results"""
# Check if results are available
if not results:
st.warning("No Google Trends results available. Please try a different query or check your API configuration.")
return
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display interest over time chart if available
interest_over_time = results.get('interest_over_time')
if interest_over_time is not None and not interest_over_time.empty:
st.markdown('<div class="category-header">Interest Over Time</div>', unsafe_allow_html=True)
st.markdown('<div class="chart-container">', unsafe_allow_html=True)
st.markdown('<div class="chart-title">Search Interest Over Time</div>', unsafe_allow_html=True)
# Convert to DataFrame if it's not already
if not isinstance(interest_over_time, pd.DataFrame):
interest_over_time = pd.DataFrame(interest_over_time)
# Prepare data for visualization
if 'date' in interest_over_time.columns:
# Melt the DataFrame to get it in the right format for plotting
terms = [col for col in interest_over_time.columns if col != 'date']
df = interest_over_time.melt('date', value_vars=terms, var_name='Term', value_name='Interest')
# Create and display the chart
fig = px.line(df, x='date', y='Interest', color='Term', title='Search Interest Over Time')
st.plotly_chart(fig, use_container_width=True)
else:
st.error("Interest over time data is not in the expected format.")
st.markdown('</div>', unsafe_allow_html=True)
# Display related queries if available
related_queries = results.get('related_queries', {})
if related_queries:
st.markdown('<div class="category-header">Related Queries</div>', unsafe_allow_html=True)
# Display top related queries
for term, queries in related_queries.items():
if 'top' in queries:
st.markdown(f'<div class="subcategory-header">Top queries for "{term}"</div>', unsafe_allow_html=True)
for query in queries['top'].get('query', []):
st.markdown(f"""
<div class="result-item">
<div class="result-title">{query}</div>
</div>
""", unsafe_allow_html=True)
if 'rising' in queries:
st.markdown(f'<div class="subcategory-header">Rising queries for "{term}"</div>', unsafe_allow_html=True)
for query in queries['rising'].get('query', []):
st.markdown(f"""
<div class="result-item">
<div class="result-title">{query}</div>
</div>
""", unsafe_allow_html=True)
# Display related topics if available
related_topics = results.get('related_topics', {})
if related_topics:
st.markdown('<div class="category-header">Related Topics</div>', unsafe_allow_html=True)
# Display top related topics
for term, topics in related_topics.items():
if 'top' in topics:
st.markdown(f'<div class="subcategory-header">Top topics for "{term}"</div>', unsafe_allow_html=True)
for topic in topics['top'].get('topic', []):
st.markdown(f"""
<div class="result-item">
<div class="result-title">{topic}</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
def display_crawler_results(results):
"""Display Web Crawler results"""
# Check if results are available
if not results:
st.warning("No web crawler results available. Please try a different URL or check your API configuration.")
return
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display crawled pages
st.markdown('<div class="category-header">Crawled Pages</div>', unsafe_allow_html=True)
# Handle different result formats
if isinstance(results, dict):
# Single page result
page_data = results
st.markdown(f"""
<div class="result-item">
<div class="result-title">{page_data.get('title', 'No Title')}</div>
<div class="result-url">{page_data.get('url', '#')}</div>
<div class="result-snippet">
<b>Title:</b> {page_data.get('title', 'No Title')}<br>
<b>Description:</b> {page_data.get('description', 'No description available.')}<br>
<b>Word Count:</b> {page_data.get('word_count', 'Unknown')}
</div>
</div>
""", unsafe_allow_html=True)
# Display content sections if available
content = page_data.get('content', [])
if content:
st.markdown('<div class="category-header">Page Content</div>', unsafe_allow_html=True)
for section in content:
if isinstance(section, dict):
section_type = section.get('type', '')
section_content = section.get('content', '')
if section_type and section_content:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{section_type}</div>
<div class="result-snippet">{section_content}</div>
</div>
""", unsafe_allow_html=True)
elif isinstance(results, list):
# Multiple pages result
for page_data in results:
if isinstance(page_data, dict):
st.markdown(f"""
<div class="result-item">
<div class="result-title">{page_data.get('title', 'No Title')}</div>
<div class="result-url">{page_data.get('url', '#')}</div>
<div class="result-snippet">
<b>Title:</b> {page_data.get('title', 'No Title')}<br>
<b>Description:</b> {page_data.get('description', 'No description available.')}<br>
<b>Word Count:</b> {page_data.get('word_count', 'Unknown')}
</div>
</div>
""", unsafe_allow_html=True)
# Display content structure
st.markdown('<div class="category-header">Content Structure</div>', unsafe_allow_html=True)
# Placeholder data for chart
labels = ['Blog Posts', 'Product Pages', 'Category Pages', 'About Pages', 'Contact Pages']
values = [38, 27, 18, 10, 7]
fig = go.Figure(data=[go.Pie(labels=labels, values=values, hole=.3)])
fig.update_layout(title_text='Content Type Distribution')
st.plotly_chart(fig, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
def display_analyzer_results(results):
"""Display Website Analyzer results"""
# This is a placeholder function that will be implemented when integrated with actual modules
st.markdown('<div class="results-container">', unsafe_allow_html=True)
# Display content quality metrics
st.markdown('<div class="category-header">Content Quality Metrics</div>', unsafe_allow_html=True)
# Placeholder data for chart
categories = ['Readability', 'Engagement', 'Relevance', 'Uniqueness', 'Comprehensiveness']
values = [4.2, 3.8, 4.5, 3.9, 4.1]
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=values,
theta=categories,
fill='toself',
name='Content Quality'
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 5]
)),
showlegend=False
)
st.plotly_chart(fig, use_container_width=True)
# Display SEO recommendations
st.markdown('<div class="category-header">SEO Recommendations</div>', unsafe_allow_html=True)
# Placeholder data
recommendations = [
"Improve meta descriptions for better click-through rates",
"Add more internal links to related content",
"Optimize images with descriptive alt text",
"Improve page loading speed by optimizing images",
"Add structured data markup for better search visibility"
]
for recommendation in recommendations:
st.markdown(f"""
<div class="result-item">
<div class="result-title">{recommendation}</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)

View File

@@ -0,0 +1,684 @@
"""
Streamlit UI for Content Performance Predictor
Interactive interface for predicting and optimizing content performance
"""
import streamlit as st
import asyncio
import json
import plotly.graph_objects as go
import plotly.express as px
from datetime import datetime, timedelta
import pandas as pd
from typing import Dict, List, Any, Optional
import logging
# Import the predictor
try:
from lib.content_performance_predictor.content_performance_predictor import (
ContentPerformancePredictor,
ContentType,
predict_content_performance
)
except ImportError:
st.error("Content Performance Predictor module not found. Please check the installation.")
logger = logging.getLogger(__name__)
class ContentPerformancePredictorUI:
"""Streamlit UI for Content Performance Predictor"""
def __init__(self):
self.predictor = None
self.initialize_predictor()
def initialize_predictor(self):
"""Initialize the predictor with error handling"""
try:
self.predictor = ContentPerformancePredictor()
except Exception as e:
st.error(f"Failed to initialize predictor: {str(e)}")
self.predictor = None
def render_main_interface(self):
"""Render the main Content Performance Predictor interface"""
st.title("🎯 Content Performance Predictor")
st.markdown("### Predict content success before you publish!")
# Create tabs for different features
tab1, tab2, tab3, tab4 = st.tabs([
"📊 Predict Performance",
"📈 Batch Analysis",
"🔧 Optimization Tools",
"📚 Performance History"
])
with tab1:
self.render_single_prediction_tab()
with tab2:
self.render_batch_analysis_tab()
with tab3:
self.render_optimization_tab()
with tab4:
self.render_history_tab()
def render_single_prediction_tab(self):
"""Render single content prediction interface"""
st.header("Single Content Analysis")
# Input section
col1, col2 = st.columns([2, 1])
with col1:
# Content input
content_input = st.text_area(
"Enter your content:",
height=200,
placeholder="Paste your content here...\n\nExample:\n🚀 Just discovered an amazing AI writing tool that's changing the game!\n\n#AIWriting #ContentCreation"
)
# Target keywords
keywords_input = st.text_input(
"Target Keywords (optional):",
placeholder="AI writing, content creation, SEO"
)
with col2:
# Content type selection
content_type = st.selectbox(
"Content Type:",
["twitter", "linkedin", "facebook", "instagram", "blog_post", "email", "youtube"],
help="Select the platform where you plan to publish"
)
# Analysis options
st.subheader("Analysis Options")
include_seo = st.checkbox("Include SEO Analysis", value=True)
include_trends = st.checkbox("Include Trend Analysis", value=True)
include_competitor = st.checkbox("Include Competitor Analysis", value=False)
# Advanced settings
with st.expander("Advanced Settings"):
target_audience = st.selectbox(
"Target Audience:",
["General", "Business", "Tech", "Marketing", "Education", "Entertainment"]
)
urgency_level = st.slider(
"Content Urgency:",
0.0, 1.0, 0.5,
help="How time-sensitive is this content?"
)
# Predict button
if st.button("🎯 Predict Performance", type="primary", use_container_width=True):
if not content_input.strip():
st.error("Please enter content to analyze")
return
# Process keywords
keywords = [k.strip() for k in keywords_input.split(",")] if keywords_input else None
# Show loading spinner
with st.spinner("Analyzing content performance..."):
# Run prediction
prediction_result = self.run_prediction(
content_input,
content_type,
keywords,
include_seo,
include_trends,
include_competitor
)
if prediction_result:
self.display_prediction_results(prediction_result)
else:
st.error("Failed to analyze content. Please try again.")
def run_prediction(
self,
content: str,
content_type: str,
keywords: Optional[List[str]],
include_seo: bool,
include_trends: bool,
include_competitor: bool
) -> Optional[Dict[str, Any]]:
"""Run the content performance prediction"""
try:
# Run async prediction
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(
predict_content_performance(
content=content,
content_type=content_type,
target_keywords=keywords
)
)
loop.close()
return result
except Exception as e:
logger.error(f"Error in prediction: {str(e)}")
st.error(f"Prediction failed: {str(e)}")
return None
def display_prediction_results(self, result: Dict[str, Any]):
"""Display the prediction results with visualizations"""
st.success("✅ Analysis Complete!")
# Overall score section
st.subheader("📊 Overall Performance Score")
col1, col2, col3 = st.columns(3)
with col1:
# Overall score gauge
score = result.get("overall_score", 0)
self.render_score_gauge(score, "Overall Score")
with col2:
# Success probability
prob = result.get("success_probability", 0) * 100
self.render_score_gauge(prob, "Success Probability")
with col3:
# Performance rating
rating = self.get_performance_rating(score)
st.metric(
"Performance Rating",
rating["label"],
help=rating["description"]
)
# Detailed scores breakdown
st.subheader("🔍 Detailed Score Breakdown")
scores = result.get("individual_scores", {})
if scores:
self.render_scores_breakdown(scores)
# Recommendations section
st.subheader("💡 Optimization Recommendations")
recommendations = result.get("recommendations", [])
if recommendations:
for i, rec in enumerate(recommendations, 1):
st.markdown(f"**{i}.** {rec}")
else:
st.info("No specific recommendations available")
# Predicted metrics
st.subheader("📊 Predicted Performance Metrics")
metrics = result.get("predicted_metrics", {})
if metrics:
self.render_predicted_metrics(metrics)
# Content analysis details
st.subheader("📝 Content Analysis Details")
analysis = result.get("content_analysis", {})
if analysis:
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Word Count", analysis.get("word_count", 0))
with col2:
st.metric("Character Count", analysis.get("character_count", 0))
with col3:
st.metric("Hashtags", analysis.get("hashtag_count", 0))
with col4:
st.metric("Readability", f"{analysis.get('readability_score', 0):.1f}")
# Export options
st.subheader("📤 Export Results")
col1, col2 = st.columns(2)
with col1:
if st.button("📄 Generate Report"):
report = self.generate_text_report(result)
st.text_area("Report:", value=report, height=200)
with col2:
if st.button("📊 Download JSON"):
json_str = json.dumps(result, indent=2)
st.download_button(
label="Download JSON",
data=json_str,
file_name=f"content_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
def render_score_gauge(self, score: float, title: str):
"""Render a gauge chart for scores"""
fig = go.Figure(go.Indicator(
mode = "gauge+number+delta",
value = score,
domain = {'x': [0, 1], 'y': [0, 1]},
title = {'text': title},
delta = {'reference': 50},
gauge = {
'axis': {'range': [None, 100]},
'bar': {'color': "darkblue"},
'steps': [
{'range': [0, 25], 'color': "lightgray"},
{'range': [25, 50], 'color': "yellow"},
{'range': [50, 75], 'color': "orange"},
{'range': [75, 100], 'color': "green"}],
'threshold': {
'line': {'color': "red", 'width': 4},
'thickness': 0.75,
'value': 90}}))
fig.update_layout(height=300)
st.plotly_chart(fig, use_container_width=True)
def render_scores_breakdown(self, scores: Dict[str, float]):
"""Render radar chart for score breakdown"""
categories = list(scores.keys())
values = list(scores.values())
# Close the radar chart
categories.append(categories[0])
values.append(values[0])
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=values,
theta=categories,
fill='toself',
name='Performance Scores'
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 100]
)),
showlegend=True,
title="Performance Score Breakdown"
)
st.plotly_chart(fig, use_container_width=True)
# Display scores as metrics
cols = st.columns(len(scores))
for i, (category, score) in enumerate(scores.items()):
with cols[i]:
st.metric(category.title(), f"{score:.1f}")
def render_predicted_metrics(self, metrics: Dict[str, Any]):
"""Render predicted performance metrics"""
cols = st.columns(len(metrics))
for i, (metric, value) in enumerate(metrics.items()):
with cols[i]:
# Format metric name
display_name = metric.replace("predicted_", "").replace("_", " ").title()
st.metric(display_name, f"{value:,}")
def get_performance_rating(self, score: float) -> Dict[str, str]:
"""Get performance rating based on score"""
if score >= 80:
return {"label": "Excellent", "description": "High chance of success"}
elif score >= 60:
return {"label": "Good", "description": "Solid performance expected"}
elif score >= 40:
return {"label": "Average", "description": "Room for improvement"}
else:
return {"label": "Needs Work", "description": "Optimization recommended"}
def render_batch_analysis_tab(self):
"""Render batch analysis interface"""
st.header("Batch Content Analysis")
st.info("Analyze multiple pieces of content at once")
# File upload
uploaded_file = st.file_uploader(
"Upload CSV with content",
type=['csv'],
help="CSV should have columns: 'content', 'content_type', 'keywords' (optional)"
)
if uploaded_file is not None:
try:
df = pd.read_csv(uploaded_file)
# Validate required columns
required_cols = ['content', 'content_type']
missing_cols = [col for col in required_cols if col not in df.columns]
if missing_cols:
st.error(f"Missing required columns: {', '.join(missing_cols)}")
return
st.success(f"✅ Loaded {len(df)} content items")
# Preview data
with st.expander("Preview Data"):
st.dataframe(df.head())
# Analysis options
col1, col2 = st.columns(2)
with col1:
max_items = st.number_input(
"Max items to analyze:",
min_value=1,
max_value=100,
value=min(10, len(df))
)
with col2:
export_format = st.selectbox(
"Export format:",
["CSV", "JSON", "Excel"]
)
# Run batch analysis
if st.button("🚀 Run Batch Analysis", type="primary"):
with st.spinner(f"Analyzing {max_items} content items..."):
batch_df = df.head(max_items)
results = self.run_batch_analysis(batch_df)
if results:
self.display_batch_results(results)
else:
st.error("Batch analysis failed")
except Exception as e:
st.error(f"Error processing file: {str(e)}")
def run_batch_analysis(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
"""Run batch analysis on multiple content items"""
results = []
progress = st.progress(0)
for i, row in df.iterrows():
try:
content = row['content']
content_type = row['content_type']
keywords = row.get('keywords', '').split(',') if row.get('keywords') else None
result = self.run_prediction(content, content_type, keywords, True, True, False)
if result:
result['original_content'] = content
result['content_type'] = content_type
results.append(result)
progress.progress((i + 1) / len(df))
except Exception as e:
st.warning(f"Error analyzing row {i}: {str(e)}")
continue
return results
def display_batch_results(self, results: List[Dict[str, Any]]):
"""Display batch analysis results"""
st.success(f"✅ Analyzed {len(results)} content items")
# Summary statistics
st.subheader("📊 Batch Summary")
scores = [r.get('overall_score', 0) for r in results]
avg_score = sum(scores) / len(scores) if scores else 0
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Average Score", f"{avg_score:.1f}")
with col2:
st.metric("Best Score", f"{max(scores):.1f}" if scores else "0")
with col3:
st.metric("Worst Score", f"{min(scores):.1f}" if scores else "0")
with col4:
good_content = len([s for s in scores if s >= 60])
st.metric("Good Content", f"{good_content}/{len(scores)}")
# Results table
st.subheader("📋 Detailed Results")
# Create summary DataFrame
summary_data = []
for i, result in enumerate(results):
summary_data.append({
"Content": result['original_content'][:50] + "..." if len(result['original_content']) > 50 else result['original_content'],
"Type": result['content_type'],
"Overall Score": result.get('overall_score', 0),
"Success Probability": result.get('success_probability', 0) * 100,
"Engagement": result.get('individual_scores', {}).get('engagement', 0),
"SEO": result.get('individual_scores', {}).get('seo', 0),
"Virality": result.get('individual_scores', {}).get('virality', 0)
})
summary_df = pd.DataFrame(summary_data)
st.dataframe(summary_df, use_container_width=True)
# Download results
if st.button("📥 Download Results"):
csv = summary_df.to_csv(index=False)
st.download_button(
label="Download CSV",
data=csv,
file_name=f"batch_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv"
)
def render_optimization_tab(self):
"""Render content optimization tools"""
st.header("🔧 Content Optimization Tools")
# A/B Testing section
st.subheader("🧪 A/B Content Testing")
col1, col2 = st.columns(2)
with col1:
st.markdown("**Version A**")
content_a = st.text_area(
"Content A:",
height=150,
key="content_a",
placeholder="Enter first version of your content..."
)
with col2:
st.markdown("**Version B**")
content_b = st.text_area(
"Content B:",
height=150,
key="content_b",
placeholder="Enter second version of your content..."
)
# Common settings
content_type = st.selectbox(
"Content Type for both:",
["twitter", "linkedin", "facebook", "instagram", "blog_post", "email", "youtube"],
key="ab_content_type"
)
if st.button("⚡ Compare Versions", type="primary"):
if not content_a.strip() or not content_b.strip():
st.error("Please enter both versions of content")
return
with st.spinner("Comparing content versions..."):
result_a = self.run_prediction(content_a, content_type, None, True, True, False)
result_b = self.run_prediction(content_b, content_type, None, True, True, False)
if result_a and result_b:
self.display_ab_comparison(result_a, result_b)
# Optimization suggestions
st.subheader("💡 Optimization Suggestions")
optimization_content = st.text_area(
"Content to optimize:",
height=150,
placeholder="Enter content for optimization suggestions..."
)
if st.button("🚀 Get Suggestions") and optimization_content.strip():
with st.spinner("Generating optimization suggestions..."):
suggestions = self.generate_optimization_suggestions(optimization_content)
if suggestions:
st.success("✅ Optimization suggestions generated!")
for i, suggestion in enumerate(suggestions, 1):
st.markdown(f"**{i}.** {suggestion}")
def display_ab_comparison(self, result_a: Dict[str, Any], result_b: Dict[str, Any]):
"""Display A/B test comparison results"""
st.success("✅ A/B Comparison Complete!")
# Overall comparison
col1, col2, col3 = st.columns(3)
score_a = result_a.get('overall_score', 0)
score_b = result_b.get('overall_score', 0)
winner = "A" if score_a > score_b else "B" if score_b > score_a else "Tie"
with col1:
st.metric("Version A Score", f"{score_a:.1f}")
with col2:
st.metric("Version B Score", f"{score_b:.1f}")
with col3:
st.metric("Winner", winner, delta=f"{abs(score_a - score_b):.1f} point difference")
# Detailed comparison chart
scores_a = result_a.get('individual_scores', {})
scores_b = result_b.get('individual_scores', {})
categories = list(scores_a.keys())
values_a = list(scores_a.values())
values_b = list(scores_b.values())
# Create comparison bar chart
fig = go.Figure(data=[
go.Bar(name='Version A', x=categories, y=values_a),
go.Bar(name='Version B', x=categories, y=values_b)
])
fig.update_layout(
barmode='group',
title="Detailed Score Comparison",
yaxis_title="Score",
xaxis_title="Category"
)
st.plotly_chart(fig, use_container_width=True)
# Recommendations comparison
st.subheader("📋 Recommendations Comparison")
col1, col2 = st.columns(2)
with col1:
st.markdown("**Version A Recommendations:**")
recs_a = result_a.get('recommendations', [])
for rec in recs_a[:5]:
st.markdown(f"{rec}")
with col2:
st.markdown("**Version B Recommendations:**")
recs_b = result_b.get('recommendations', [])
for rec in recs_b[:5]:
st.markdown(f"{rec}")
def generate_optimization_suggestions(self, content: str) -> List[str]:
"""Generate optimization suggestions for content"""
suggestions = []
# Basic content analysis
word_count = len(content.split())
char_count = len(content)
hashtag_count = content.count('#')
# Length optimization
if word_count < 10:
suggestions.append("Consider adding more detail to your content for better engagement")
elif word_count > 50:
suggestions.append("Consider shortening your content for better social media performance")
# Hashtag optimization
if hashtag_count == 0:
suggestions.append("Add relevant hashtags to increase discoverability")
elif hashtag_count > 5:
suggestions.append("Reduce the number of hashtags for better readability")
# Engagement optimization
if '?' not in content:
suggestions.append("Consider adding a question to encourage engagement")
if '!' not in content and '.' in content:
suggestions.append("Add some excitement with exclamation marks")
# Call to action
cta_words = ['click', 'share', 'comment', 'like', 'follow', 'subscribe']
has_cta = any(word in content.lower() for word in cta_words)
if not has_cta:
suggestions.append("Include a clear call-to-action (like, share, comment)")
# Emoji usage
emoji_count = len([char for char in content if ord(char) > 127])
if emoji_count == 0:
suggestions.append("Consider adding relevant emojis to make content more engaging")
return suggestions[:5] # Limit to top 5 suggestions
def render_history_tab(self):
"""Render performance history interface"""
st.header("📚 Performance History")
st.info("Performance history tracking coming soon!")
st.markdown("This feature will allow you to:")
st.markdown("• Track your content performance over time")
st.markdown("• Compare predicted vs actual performance")
st.markdown("• Identify your best-performing content patterns")
st.markdown("• Generate performance reports")
def generate_text_report(self, result: Dict[str, Any]) -> str:
"""Generate a text report of the analysis"""
report = f"""
CONTENT PERFORMANCE ANALYSIS REPORT
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
OVERALL PERFORMANCE
Overall Score: {result.get('overall_score', 0):.1f}/100
Success Probability: {result.get('success_probability', 0)*100:.1f}%
Performance Rating: {self.get_performance_rating(result.get('overall_score', 0))['label']}
DETAILED SCORES
"""
scores = result.get('individual_scores', {})
for category, score in scores.items():
report += f"{category.title()}: {score:.1f}/100\n"
report += "\nCONTENT ANALYSIS\n"
analysis = result.get('content_analysis', {})
for key, value in analysis.items():
report += f"{key.replace('_', ' ').title()}: {value}\n"
report += "\nRECOMMENDATIONS\n"
recommendations = result.get('recommendations', [])
for i, rec in enumerate(recommendations, 1):
report += f"{i}. {rec}\n"
return report
def render_content_performance_predictor():
"""Main function to render the Content Performance Predictor UI"""
ui = ContentPerformancePredictorUI()
ui.render_main_interface()
if __name__ == "__main__":
render_content_performance_predictor()

View File

@@ -0,0 +1,510 @@
import streamlit as st
def apply_dashboard_style():
"""Apply common glassmorphic styling for all dashboards (AI Writers, SEO Tools, Social Media)."""
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* Global styling with glassmorphic background */
.stApp {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
.main .block-container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 2rem 2.5rem 3rem 2.5rem !important;
max-width: 1400px;
margin: 2rem auto;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
/* Modern dashboard header with glassmorphic effect */
.dashboard-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 2rem;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.dashboard-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
.dashboard-header h1 {
font-size: 3.2em;
font-weight: 700;
color: #ffffff;
margin-bottom: 1rem;
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
letter-spacing: -0.02em;
line-height: 1.1;
}
.dashboard-header p {
font-size: 1.2em;
color: rgba(255, 255, 255, 0.85);
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
font-weight: 400;
}
/* Category headers with modern styling */
.stMarkdown h2 {
color: #ffffff;
font-size: 1.8em;
font-weight: 600;
margin: 2.5rem 0 1.5rem 0;
padding-left: 1rem;
border-left: 4px solid rgba(255, 255, 255, 0.6);
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
letter-spacing: -0.01em;
}
/* Custom category headers */
.category-header {
margin: 3rem 0 2rem 0;
position: relative;
}
.category-header h2 {
color: #ffffff;
font-size: 1.8em;
font-weight: 600;
margin: 0 0 0.5rem 0;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
letter-spacing: -0.01em;
}
.category-line {
height: 2px;
background: linear-gradient(90deg, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0.2) 50%, transparent 100%);
border-radius: 1px;
margin-bottom: 1.5rem;
}
/* Cards container with grid layout */
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
/* Card container */
.card-container {
position: relative;
margin-bottom: 1.5rem;
}
/* Premium cards (works for both writer and tool cards) */
.premium-card {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 20px;
padding: 0;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
position: relative;
overflow: hidden;
min-height: 200px;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
transform: translateY(0) scale(1);
margin-bottom: 1rem;
}
/* Style all Streamlit buttons to match the glassmorphic design */
.card-container + div[data-testid="stButton"] > button,
.stButton > button {
background: rgba(255, 255, 255, 0.08) !important;
backdrop-filter: blur(20px) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-radius: 20px !important;
color: #ffffff !important;
font-family: 'Inter', sans-serif !important;
font-weight: 500 !important;
font-size: 1rem !important;
padding: 1rem 1.5rem !important;
min-height: 60px !important;
margin: 0 !important;
box-shadow:
0 8px 25px rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important;
position: relative !important;
overflow: hidden !important;
width: 100% !important;
}
/* Button hover effects */
.card-container + div[data-testid="stButton"] > button:hover,
.stButton > button:hover {
background: rgba(255, 255, 255, 0.12) !important;
backdrop-filter: blur(25px) !important;
transform: translateY(-4px) scale(1.02) !important;
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
color: #ffffff !important;
}
/* Button focus state */
.card-container + div[data-testid="stButton"] > button:focus,
.stButton > button:focus {
outline: 2px solid rgba(255, 255, 255, 0.5) !important;
outline-offset: 2px !important;
background: rgba(255, 255, 255, 0.12) !important;
}
/* Button active state */
.card-container + div[data-testid="stButton"] > button:active,
.stButton > button:active {
transform: translateY(-2px) scale(1.01) !important;
transition: all 0.1s ease !important;
}
/* Add shine effect to buttons */
.card-container + div[data-testid="stButton"] > button::before,
.stButton > button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transform: skewX(-20deg);
transition: left 0.6s ease;
pointer-events: none;
}
.card-container + div[data-testid="stButton"] > button:hover::before,
.stButton > button:hover::before {
left: 100%;
}
/* Card hover effects when hovering over the container */
.card-container:hover .premium-card {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(25px);
transform: translateY(-4px) scale(1.02);
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.3);
}
.card-container:hover .card-glow {
opacity: 1;
}
.card-container:hover .card-arrow {
transform: translateX(5px);
color: #ffffff;
}
.card-container:hover .card-shine {
left: 100%;
}
.card-container:hover .card-icon {
filter: drop-shadow(0 0 15px rgba(255, 255, 255, 0.5));
transform: scale(1.05);
}
.card-container:hover .card-category {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
}
/* Active/pressed state */
.card-container:active .premium-card {
transform: translateY(-2px) scale(1.01);
transition: all 0.1s ease;
}
/* Card glow effect */
.card-glow {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
border-radius: 22px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
}
/* Card content wrapper */
.card-content {
padding: 2rem 1.8rem;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
z-index: 2;
}
/* Card icon */
.card-icon {
font-size: 2.8em;
margin-bottom: 1rem;
color: #ffffff;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.3));
line-height: 1;
transition: all 0.3s ease;
}
/* Card title */
.card-title {
font-size: 1.3em;
font-weight: 600;
color: #ffffff;
margin-bottom: 0.8rem;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
letter-spacing: -0.01em;
line-height: 1.3;
}
/* Card description */
.card-description {
font-size: 0.95em;
color: rgba(255, 255, 255, 0.85);
line-height: 1.5;
margin-bottom: 1.5rem;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
flex-grow: 1;
}
/* Card footer */
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
/* Card category badge */
.card-category {
background: rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.9);
padding: 0.3rem 0.8rem;
border-radius: 12px;
font-size: 0.8em;
font-weight: 500;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
/* Card arrow */
.card-arrow {
font-size: 1.2em;
color: rgba(255, 255, 255, 0.7);
transition: all 0.3s ease;
transform: translateX(0);
}
/* Card shine effect */
.card-shine {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transform: skewX(-20deg);
transition: left 0.6s ease;
pointer-events: none;
}
/* Category spacer */
.category-spacer {
height: 2rem;
}
/* Responsive adjustments for cards */
@media (max-width: 768px) {
.cards-container {
grid-template-columns: 1fr;
gap: 1rem;
}
.card-content {
padding: 1.5rem;
}
.premium-card {
min-height: 180px;
}
.category-header {
margin: 2rem 0 1.5rem 0;
}
.main .block-container {
padding: 1.5rem !important;
margin: 1rem;
}
.dashboard-header {
padding: 2rem 1.5rem;
margin-bottom: 2rem;
}
.dashboard-header h1 {
font-size: 2.4em;
}
.dashboard-header p {
font-size: 1.1em;
}
}
/* Card entrance animations */
.premium-card {
animation: cardSlideIn 0.6s ease-out forwards;
opacity: 0;
}
@keyframes cardSlideIn {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Stagger animation delays */
.premium-card:nth-child(1) { animation-delay: 0.1s; }
.premium-card:nth-child(2) { animation-delay: 0.2s; }
.premium-card:nth-child(3) { animation-delay: 0.3s; }
.premium-card:nth-child(4) { animation-delay: 0.4s; }
.premium-card:nth-child(5) { animation-delay: 0.5s; }
.premium-card:nth-child(6) { animation-delay: 0.6s; }
/* Hide Streamlit elements */
.stApp > header {
visibility: hidden;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* Page entrance animation */
.main .block-container {
animation: fadeInUp 0.8s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
""", unsafe_allow_html=True)
def render_dashboard_header(title, description):
"""Render a standardized dashboard header."""
st.markdown(f"""
<div class="dashboard-header">
<h1>{title}</h1>
<p>{description}</p>
</div>
""", unsafe_allow_html=True)
def render_category_header(category_name):
"""Render a standardized category header."""
st.markdown(f"""
<div class="category-header">
<h2>{category_name}</h2>
<div class="category-line"></div>
</div>
""", unsafe_allow_html=True)
def render_card(icon, title, description, category, key_suffix="", help_text=""):
"""Render a standardized premium card with button."""
st.markdown(f"""
<div class="card-container">
<div class="premium-card">
<div class="card-glow"></div>
<div class="card-content">
<div class="card-icon">{icon}</div>
<div class="card-title">{title}</div>
<div class="card-description">{description}</div>
<div class="card-footer">
<span class="card-category">{category}</span>
<div class="card-arrow">→</div>
</div>
</div>
<div class="card-shine"></div>
</div>
</div>
""", unsafe_allow_html=True)
# Return button for functionality
return st.button(
f"Select {title}",
key=f"card_{key_suffix}",
help=help_text or f"Launch {title} - {description}",
use_container_width=True
)

View File

@@ -0,0 +1,277 @@
import streamlit as st
import logging
from datetime import datetime
from typing import Dict, Optional, Any
# Configure module logger
logger = logging.getLogger(__name__)
def display_research_results(results: Dict[str, Any]) -> None:
"""
Display research results in a structured format with tabs.
Args:
results (dict): Processed research results containing summary and data
"""
if not results:
st.warning("No results to display")
return
# Create tabs for different result sections
tabs = st.tabs(["📊 Summary", "🔍 Results", "📈 Statistics"])
with tabs[0]:
display_summary_section(results)
with tabs[1]:
if results['source'] == 'gemini':
display_gemini_results(results)
else:
display_serp_results(results)
with tabs[2]:
display_statistics(results)
def process_research_results(results: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Process and format research results."""
logger.info("Processing research results")
try:
if not results:
return None
processed = {
'timestamp': str(datetime.now()),
'source': results.get('source', 'unknown'),
'summary': {},
'data': {}
}
if results.get('source') == 'gemini':
processed.update(process_gemini_results(results))
else:
processed.update(process_serp_results(results))
logger.info("Results processing completed")
return processed
except Exception as err:
logger.error(f"Failed to process results: {err}", exc_info=True)
return None
def process_search_results(search_results: Dict[str, Any], search_type: str = "general") -> Optional[Dict[str, Any]]:
"""Process search results and prepare for display."""
logger.info(f"Processing {search_type} search results")
try:
if not search_results:
return None
processed = {
'organic': process_organic_results(search_results.get('organic', [])),
'peopleAlsoAsk': process_paa_results(search_results.get('peopleAlsoAsk', [])),
'relatedSearches': process_related_searches(search_results.get('relatedSearches', [])),
'metadata': {
'timestamp': str(datetime.now()),
'type': search_type
}
}
return processed
except Exception as err:
logger.error(f"Error processing search results: {err}", exc_info=True)
return None
# Helper functions for result processing
def process_organic_results(results):
"""Process organic search results."""
return [{
'title': result.get('title', 'No Title'),
'link': result.get('link', '#'),
'snippet': result.get('snippet', 'No snippet available'),
'position': result.get('position', 'N/A')
} for result in results]
def process_paa_results(results):
"""Process People Also Ask results."""
return [{
'question': result.get('title', ''),
'answer': result.get('snippet', 'No answer available'),
'link': result.get('link', '#')
} for result in results]
def process_related_searches(results):
"""Process related searches."""
return [query.get('query', '') for query in results]
def process_gemini_results(results: Dict[str, Any]) -> Dict[str, Any]:
"""
Process Gemini API research results.
Args:
results (dict): Raw Gemini research results
Returns:
dict: Processed results with summary and data
"""
gemini_data = results.get('results', {})
return {
'summary': {
'main_findings': gemini_data.get('main_response', ''),
'sources': gemini_data.get('grounding_data', []),
'processing_time': gemini_data.get('metadata', {}).get('timestamp'),
'total_sources': len(gemini_data.get('grounding_data', [])),
'model': gemini_data.get('metadata', {}).get('model', 'unknown')
},
'data': gemini_data
}
def process_serp_results(results: Dict[str, Any]) -> Dict[str, Any]:
"""
Process SERP search results.
Args:
results (dict): Raw SERP results
Returns:
dict: Processed results with summary and data
"""
organic_results = results.get('organic', [])
paa_results = results.get('peopleAlsoAsk', [])
related_searches = results.get('relatedSearches', [])
return {
'summary': {
'total_results': len(organic_results),
'sources': [result.get('link') for result in organic_results],
'titles': [result.get('title') for result in organic_results],
'total_questions': len(paa_results),
'total_related': len(related_searches)
},
'data': {
'organic': process_organic_results(organic_results),
'peopleAlsoAsk': process_paa_results(paa_results),
'relatedSearches': process_related_searches(related_searches)
}
}
# Display helper functions
def display_summary_section(results):
"""Display summary section of results."""
st.markdown("### 📋 Research Summary")
st.markdown(f"""
- **Source**: {results['source'].title()}
- **Time**: {results['timestamp']}
- **Total Sources**: {len(results.get('summary', {}).get('sources', []))}
""")
def display_gemini_results(results):
"""Display Gemini-specific results."""
st.markdown("### 🤖 Gemini Research Findings")
st.write(results['summary']['main_findings'])
with st.expander("🌐 Sources and References", expanded=False):
st.write(results['data'].get('grounding_data', 'No sources available'))
def display_serp_results(results):
"""Display SERP-specific results."""
st.markdown("### 🔍 Search Results")
for result in results['data'].get('organic', []):
with st.expander(f"📄 {result['title']}", expanded=False):
st.markdown(f"""
**Rank:** {result['position']}
**Link:** [{result['link']}]({result['link']})
**Snippet:**
{result['snippet']}
""")
def display_statistics(results: Dict[str, Any]) -> None:
"""
Display statistical information about search results.
Args:
results (dict): Processed research results
"""
st.markdown("### 📈 Research Statistics")
# Source-specific metrics
if results['source'] == 'gemini':
col1, col2 = st.columns(2)
with col1:
st.metric(
"Sources Analyzed",
results.get('summary', {}).get('total_sources', 0)
)
with col2:
st.metric(
"Model Used",
results.get('summary', {}).get('model', 'Unknown')
)
else: # SERP results
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Organic Results",
results.get('summary', {}).get('total_results', 0)
)
with col2:
st.metric(
"Related Questions",
results.get('summary', {}).get('total_questions', 0)
)
with col3:
st.metric(
"Related Searches",
results.get('summary', {}).get('total_related', 0)
)
# Common metrics
st.markdown("#### 🕒 Timing Information")
st.info(f"Research completed at: {results['timestamp']}")
# Display data quality metrics
st.markdown("#### 📊 Data Quality")
quality_metrics = calculate_quality_metrics(results)
col1, col2 = st.columns(2)
with col1:
st.progress(quality_metrics['completeness'])
st.caption("Data Completeness")
with col2:
st.progress(quality_metrics['relevance'])
st.caption("Estimated Relevance")
def calculate_quality_metrics(results: Dict[str, Any]) -> Dict[str, float]:
"""
Calculate quality metrics for the research results.
Args:
results (dict): Processed research results
Returns:
dict: Quality metrics including completeness and relevance scores
"""
try:
if results['source'] == 'gemini':
completeness = 1.0 if results['summary']['main_findings'] else 0.0
relevance = 0.8 if results['summary']['sources'] else 0.4
else:
organic_results = results.get('summary', {}).get('total_results', 0)
completeness = min(organic_results / 10, 1.0) # Normalize to 0-1
has_paa = bool(results.get('summary', {}).get('total_questions', 0))
has_related = bool(results.get('summary', {}).get('total_related', 0))
relevance = (0.6 + (0.2 if has_paa else 0) + (0.2 if has_related else 0))
return {
'completeness': completeness,
'relevance': relevance
}
except Exception as err:
logger.error(f"Error calculating quality metrics: {err}")
return {'completeness': 0.0, 'relevance': 0.0}

View File

@@ -0,0 +1,458 @@
"""
Module for displaying Google Trends data in the Streamlit UI.
This module provides functions for visualizing Google Trends data, including:
- Interest over time
- Regional interest
- Related queries
- Related topics
"""
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import logging
# Set up logging
logger = logging.getLogger(__name__)
def display_google_trends_data(trends_data, search_keyword):
"""
Display Google Trends data in a structured format with tabs for different sections.
Args:
trends_data (dict): Dictionary containing Google Trends data
search_keyword (str): The search keyword used for the analysis
"""
if not trends_data:
st.warning("No Google Trends data available for this search.")
return
st.subheader(f"Google Trends Analysis for '{search_keyword}'")
# Add an informative message about Google Trends
with st.expander(" About Google Trends Data", expanded=False):
st.markdown("""
**What is Google Trends?**
Google Trends is a public web facility that shows how often a particular search-term is entered relative to the total search-volume across various regions of the world, and in various languages.
**What data is shown here?**
- **Related Keywords**: Terms that are frequently searched together with your keyword
- **Interest Over Time**: How interest in your keyword has changed over the past 12 months
- **Regional Interest**: Where in the world your keyword is most popular
- **Related Queries**: What people search for before and after searching for your keyword
- **Related Topics**: Topics that are closely related to your keyword
**How to interpret the data:**
- Interest values range from 0 to 100, where 100 is the peak popularity for the term
- A value of 50 means the term is half as popular as the peak
- A value of 0 means there was not enough data for this term
""")
# Create tabs for different sections
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"Related Keywords",
"Interest Over Time",
"Regional Interest",
"Related Queries",
"Related Topics"
])
with tab1:
display_keywords_section(trends_data.get('related_keywords', []))
with tab2:
display_interest_over_time(trends_data.get('interest_over_time', pd.DataFrame()))
with tab3:
display_regional_interest(trends_data.get('regional_interest', pd.DataFrame()))
with tab4:
display_related_queries(trends_data.get('related_queries', pd.DataFrame()))
with tab5:
display_related_topics(trends_data.get('related_topics', pd.DataFrame()))
# Add a footer with data source information
st.markdown("---")
st.caption("Data source: Google Trends | Last updated: " + pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S"))
def display_keywords_section(keywords):
"""Display related keywords from Google Trends in a table format."""
if not keywords:
st.info("No related keywords data available.")
return
st.subheader("Related Keywords")
st.write("Keywords related to your search:")
# Add explanation about related keywords
with st.expander(" About Related Keywords", expanded=False):
st.markdown("""
**What are Related Keywords?**
Related keywords are terms that are frequently searched together with your main keyword.
These keywords can help you understand what topics are associated with your search term
and can be valuable for content planning and SEO strategies.
**How to use this data:**
- Use these keywords to expand your content strategy
- Identify gaps in your content that you could fill
- Understand what your audience is interested in
- Improve your SEO by incorporating these terms naturally in your content
""")
# Create a DataFrame for better display
df = pd.DataFrame(keywords, columns=['Keyword'])
st.dataframe(df, use_container_width=True)
# Add a note about the number of keywords
st.caption(f"Found {len(keywords)} related keywords")
def display_interest_over_time(interest_df):
"""Display a chart showing interest over time for a given search keyword."""
if interest_df.empty:
st.info("No interest over time data available.")
return
st.subheader("Interest Over Time")
# Add explanation about interest over time
with st.expander(" About Interest Over Time", expanded=False):
st.markdown("""
**What is Interest Over Time?**
Interest Over Time shows how interest in your search term has changed over the past 12 months.
The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity
for the term, 50 means the term is half as popular, and 0 means there was not enough data.
**How to interpret this chart:**
- Look for peaks and valleys to identify trends
- Compare with seasonal patterns or events
- Identify if interest is growing, declining, or stable
- Use this data to time your content releases for maximum impact
""")
try:
# Ensure we have the required columns
if 'date' not in interest_df.columns:
st.error("Interest over time data is missing the 'date' column.")
return
if 'interest' not in interest_df.columns:
st.error("Interest over time data is missing the 'interest' column.")
return
# Create the chart
fig = px.line(
interest_df,
x='date',
y='interest',
title='Interest Over Time',
labels={'date': 'Date', 'interest': 'Interest'},
line_shape='spline'
)
fig.update_layout(
xaxis_title="Date",
yaxis_title="Interest",
hovermode='x unified'
)
st.plotly_chart(fig, use_container_width=True)
# Add summary statistics
if not interest_df.empty:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Average Interest", f"{interest_df['interest'].mean():.1f}")
with col2:
st.metric("Peak Interest", f"{interest_df['interest'].max():.1f}")
with col3:
st.metric("Lowest Interest", f"{interest_df['interest'].min():.1f}")
except Exception as e:
st.error(f"Error displaying interest over time chart: {str(e)}")
logger.error(f"Error in display_interest_over_time: {e}")
def display_regional_interest(regional_df):
"""Display a chart showing interest by region for the search keyword."""
if regional_df.empty:
st.info("No regional interest data available.")
return
st.subheader("Regional Interest")
# Add explanation about regional interest
with st.expander(" About Regional Interest", expanded=False):
st.markdown("""
**What is Regional Interest?**
Regional Interest shows how interest in your search term varies across different countries.
The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity
for the term in that region, 50 means the term is half as popular, and 0 means there was not enough data.
**How to interpret this map:**
- Darker colors indicate higher interest in that region
- Lighter colors indicate lower interest
- Hover over a country to see the exact interest value
- Use this data to target your content to specific regions
""")
try:
# Ensure we have the required columns
if 'country_code' not in regional_df.columns:
st.error("Regional interest data is missing the 'country_code' column.")
return
if 'interest' not in regional_df.columns:
st.error("Regional interest data is missing the 'interest' column.")
return
# Create the choropleth map
fig = go.Figure(data=go.Choropleth(
locations=regional_df['country_code'],
z=regional_df['interest'],
text=regional_df['country_code'], # This will show in the hover text
colorscale='Viridis',
colorbar_title="Interest Level",
zmin=0,
zmax=100,
marker_line_color='darkgray',
marker_line_width=0.5,
showscale=True,
colorbar=dict(
title="Interest Level",
tickformat=".0f",
tickmode="linear",
tick0=0,
dtick=20
)
))
# Update the layout for better visualization
fig.update_layout(
title=dict(
text='Regional Interest Distribution',
x=0.5,
xanchor='center'
),
geo=dict(
showframe=False,
showcoastlines=True,
projection_type='equirectangular',
showland=True,
landcolor='lightgray',
showocean=True,
oceancolor='aliceblue',
showcountries=True,
countrycolor='darkgray'
),
width=800,
height=500,
margin=dict(l=0, r=0, t=30, b=0)
)
# Display the map
st.plotly_chart(fig, use_container_width=True)
# Display top 5 countries with highest interest
if not regional_df.empty:
st.subheader("Top Regions by Interest")
top_regions = regional_df.sort_values('interest', ascending=False).head(5)
# Create a more visually appealing bar chart for top regions
fig_bar = go.Figure(data=[
go.Bar(
x=top_regions['country_code'],
y=top_regions['interest'],
text=top_regions['interest'].round(1),
textposition='auto',
marker_color='rgb(55, 83, 109)'
)
])
fig_bar.update_layout(
title='Top 5 Regions by Interest Level',
xaxis_title='Region',
yaxis_title='Interest Level',
yaxis_range=[0, 100],
showlegend=False
)
st.plotly_chart(fig_bar, use_container_width=True)
except Exception as e:
st.error(f"Error displaying regional interest chart: {str(e)}")
logger.error(f"Error in display_regional_interest: {e}")
def display_related_queries(queries_df):
"""Display related queries in a structured format."""
if queries_df.empty:
st.info("No related queries data available.")
return
st.subheader("Related Queries")
# Add explanation about related queries
with st.expander(" About Related Queries", expanded=False):
st.markdown("""
**What are Related Queries?**
Related Queries show what people search for before and after searching for your keyword.
These queries can help you understand the search intent and context around your keyword.
**How to interpret this data:**
- The 'value' column shows the relative interest compared to your main keyword
- Higher values indicate stronger association with your keyword
- Use these queries to expand your content strategy
- Identify what questions your audience is trying to answer
""")
try:
# Ensure we have the required columns
if 'query' not in queries_df.columns:
st.error("Related queries data is missing the 'query' column.")
return
if 'value' not in queries_df.columns:
st.error("Related queries data is missing the 'value' column.")
return
# Sort by value in descending order
queries_df = queries_df.sort_values('value', ascending=False)
# Display as a table
st.dataframe(queries_df, use_container_width=True)
# Add a note about the number of queries
st.caption(f"Found {len(queries_df)} related queries")
except Exception as e:
st.error(f"Error displaying related queries: {str(e)}")
logger.error(f"Error in display_related_queries: {e}")
def display_trending_searches(trending_df):
"""Display trending searches in the UI."""
if trending_df.empty:
st.info("No trending searches data available.")
return
st.subheader("📊 Trending Searches")
# Display as numbered list with emojis
for idx, search in enumerate(trending_df[0].head(10), 1):
st.write(f"{idx}. 🔍 {search}")
def display_realtime_trends(trends_df):
"""Display realtime trending searches in the UI."""
if trends_df.empty:
st.info("No realtime trends data available.")
return
st.subheader("⚡ Realtime Trends")
# Create tabs for different categories
if not trends_df.empty:
# Display top 5 trends with their titles and articles
for _, row in trends_df.head(5).iterrows():
with st.expander(f"🔥 {row.get('title', 'Trending Topic')}"):
st.write(f"**Traffic:** {row.get('traffic', 'N/A')}")
if 'articles' in row:
st.write("📰 Related Articles:")
for article in row['articles'][:3]: # Show top 3 articles
st.write(f"- {article['title']}")
def display_related_topics(topics_df):
"""Display related topics in a structured format."""
if topics_df.empty:
st.info("No related topics data available.")
return
st.subheader("Related Topics")
# Add explanation about related topics
with st.expander(" About Related Topics", expanded=False):
st.markdown("""
**What are Related Topics?**
Related Topics show broader topics that are associated with your search term.
These topics can help you understand the broader context and themes related to your keyword.
**How to interpret this data:**
- The 'value' column shows the relative interest compared to your main keyword
- Higher values indicate stronger association with your keyword
- Use these topics to understand the broader context of your keyword
- Identify themes that might be relevant to your content strategy
""")
try:
# Ensure we have the required columns
if 'topic' not in topics_df.columns:
st.error("Related topics data is missing the 'topic' column.")
return
if 'value' not in topics_df.columns:
st.error("Related topics data is missing the 'value' column.")
return
# Sort by value in descending order
topics_df = topics_df.sort_values('value', ascending=False)
# Display as a table
st.dataframe(topics_df, use_container_width=True)
# Add a note about the number of topics
st.caption(f"Found {len(topics_df)} related topics")
except Exception as e:
st.error(f"Error displaying related topics: {str(e)}")
logger.error(f"Error in display_related_topics: {e}")
def process_trends_data(trends_data):
"""
Process and format Google Trends data for display.
Args:
trends_data (dict): Raw Google Trends data
Returns:
dict: Formatted data ready for display
"""
if not trends_data:
return {}
processed_data = {}
# Process related keywords
if 'related_keywords' in trends_data:
processed_data['related_keywords'] = trends_data['related_keywords']
# Process interest over time
if 'interest_over_time' in trends_data and not trends_data['interest_over_time'].empty:
processed_data['interest_over_time'] = trends_data['interest_over_time']
# Process regional interest
if 'regional_interest' in trends_data and not trends_data['regional_interest'].empty:
processed_data['regional_interest'] = trends_data['regional_interest']
# Process related queries
if 'related_queries' in trends_data and not trends_data['related_queries'].empty:
processed_data['related_queries'] = trends_data['related_queries']
# Process related topics
if 'related_topics' in trends_data and not trends_data['related_topics'].empty:
processed_data['related_topics'] = trends_data['related_topics']
return processed_data

View File

@@ -0,0 +1,585 @@
import os
import time
import logging
import streamlit as st
from datetime import datetime
from lib.ai_web_researcher.gpt_online_researcher import gpt_web_researcher
from lib.utils.read_main_config_params import read_return_config_section
# Configure module-level logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Create console handler if it doesn't exist
if not logger.handlers:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
def reload_env_variables():
"""Reload environment variables from .env file."""
try:
from dotenv import load_dotenv
load_dotenv(override=True)
return True
except Exception as e:
logger.error(f"Failed to reload environment variables: {str(e)}")
return False
def save_api_key_to_env(key_name, key_value):
"""Save API key to .env file."""
try:
env_path = os.path.join(os.getcwd(), '.env')
# Read existing .env content
existing_content = {}
if os.path.exists(env_path):
with open(env_path, 'r') as f:
for line in f:
if '=' in line:
key, value = line.strip().split('=', 1)
existing_content[key] = value
# Update or add new key
existing_content[key_name] = key_value
# Write back to .env
with open(env_path, 'w') as f:
for key, value in existing_content.items():
f.write(f"{key}={value}\n")
# Update environment variable and reload all env vars
os.environ[key_name] = key_value
if reload_env_variables():
return True
return False
except Exception as e:
logger.error(f"Failed to save API key to .env: {str(e)}")
return False
def validate_api_keys():
"""Validate required API keys and return their status."""
logger.info("Validating API keys")
# Get API keys
api_keys = {
'SERPER_API_KEY': os.getenv('SERPER_API_KEY'),
'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'),
'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'),
'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY')
}
# Test SERPER_API_KEY validity
if api_keys['SERPER_API_KEY']:
try:
# Make a test request
import requests
test_url = "https://google.serper.dev/search"
headers = {
'X-API-KEY': api_keys['SERPER_API_KEY'],
'Content-Type': 'application/json'
}
test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
api_keys['SERPER_API_KEY_VALID'] = response.status_code == 200
if not api_keys['SERPER_API_KEY_VALID']:
logger.error(f"SERPER_API_KEY validation failed: {response.status_code} - {response.text}")
except Exception as e:
logger.error(f"Error validating SERPER_API_KEY: {str(e)}")
api_keys['SERPER_API_KEY_VALID'] = False
else:
api_keys['SERPER_API_KEY_VALID'] = False
return api_keys
def do_web_research():
"""Main function to perform web research based on user input."""
# Reset session state variables for this research operation
if 'metaphor_results_displayed' in st.session_state:
del st.session_state.metaphor_results_displayed
logger.info("Starting do_web_research function")
try:
# Get API keys without validation
api_keys = {
'SERPER_API_KEY': os.getenv('SERPER_API_KEY'),
'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'),
'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'),
'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY')
}
if not api_keys['SERPER_API_KEY']:
st.error("""
🚫 SERPER_API_KEY is missing. Please configure your API key.
""")
with st.popover("⚙️ Configure API Keys"):
st.markdown("""
### API Key Configuration
Enter your API keys below to enable research features.
""")
# SERPER API Key
serper_col1, serper_col2 = st.columns([3, 1])
with serper_col1:
serper_key = st.text_input(
"Serper API Key",
type="password",
placeholder="Enter your Serper API key",
help="Get your key at https://serper.dev"
)
test_key = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_serper_key")
with serper_col2:
if st.button("Save Serper", use_container_width=True):
if serper_key:
if test_key:
# Test the API key
try:
import requests
test_url = "https://google.serper.dev/search"
headers = {
'X-API-KEY': serper_key,
'Content-Type': 'application/json'
}
test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('SERPER_API_KEY', serper_key):
st.success("✅ Serper API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('SERPER_API_KEY', serper_key):
st.success("✅ Serper API key saved!")
time.sleep(0.5) # Small delay to ensure the key is saved
st.rerun()
else:
st.error("Failed to save API key")
# METAPHOR API Key
if not api_keys.get('METAPHOR_API_KEY'):
metaphor_col1, metaphor_col2 = st.columns([3, 1])
with metaphor_col1:
metaphor_key = st.text_input(
"Metaphor API Key",
type="password",
placeholder="Enter your Metaphor API key",
help="Get your key at https://metaphor.systems"
)
test_metaphor = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_metaphor_key")
with metaphor_col2:
if st.button("Save Metaphor", use_container_width=True):
if metaphor_key:
if test_metaphor:
# Test the API key
try:
import requests
test_url = "https://api.metaphor.systems/v1/search"
headers = {
'Authorization': f'Bearer {metaphor_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "numResults": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key):
st.success("✅ Metaphor API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key):
st.success("✅ Metaphor API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
# TAVILY API Key
if not api_keys.get('TAVILY_API_KEY'):
tavily_col1, tavily_col2 = st.columns([3, 1])
with tavily_col1:
tavily_key = st.text_input(
"Tavily API Key",
type="password",
placeholder="Enter your Tavily API key",
help="Get your key at https://tavily.com"
)
test_tavily = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_tavily_key")
with tavily_col2:
if st.button("Save Tavily", use_container_width=True):
if tavily_key:
if test_tavily:
# Test the API key
try:
import requests
test_url = "https://api.tavily.com/v1/search"
headers = {
'Authorization': f'Bearer {tavily_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "max_results": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('TAVILY_API_KEY', tavily_key):
st.success("✅ Tavily API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('TAVILY_API_KEY', tavily_key):
st.success("✅ Tavily API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
# FIRECRAWL API Key
if not api_keys.get('FIRECRAWL_API_KEY'):
firecrawl_col1, firecrawl_col2 = st.columns([3, 1])
with firecrawl_col1:
firecrawl_key = st.text_input(
"Firecrawl API Key",
type="password",
placeholder="Enter your Firecrawl API key",
help="Get your key at https://firecrawl.co"
)
test_firecrawl = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_firecrawl_key")
with firecrawl_col2:
if st.button("Save Firecrawl", use_container_width=True):
if firecrawl_key:
if test_firecrawl:
# Test the API key
try:
import requests
test_url = "https://api.firecrawl.co/v1/search"
headers = {
'Authorization': f'Bearer {firecrawl_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "limit": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key):
st.success("✅ Firecrawl API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key):
st.success("✅ Firecrawl API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
st.markdown("""
---
### Need Help?
1. Click the links above to get your API keys
2. Enter the keys in the fields above
3. Click Save to store them securely
4. The app will refresh automatically
""")
return
# Initialize session state for research options
if "research_options" not in st.session_state:
st.session_state.research_options = {
"primary_keywords": "",
"related_keywords": "",
"target_audience": ["General"],
"content_type": ["Blog Posts"],
"search_depth": 3,
"geo_location": "us",
"search_language": "en",
"num_results": 10,
"time_range": "past month",
"include_domains": "",
"similar_url": "",
"search_mode": "google" # Default search mode
}
# Define the research options dialog function
@st.dialog("🔍 Research Options", width="large")
def show_research_options():
tab1, tab2 = st.tabs(["Basic", "Advanced"])
with tab1:
st.session_state.research_options["related_keywords"] = st.text_input(
"Related Keywords",
value=st.session_state.research_options["related_keywords"],
placeholder="Enter related terms...",
help="Additional keywords to provide context and expand research"
)
st.session_state.research_options["target_audience"] = st.multiselect(
"Target Audience",
["General", "Technical", "Business", "Academic", "Youth", "Senior"],
default=st.session_state.research_options["target_audience"],
help="Select your target audience to focus research"
)
st.session_state.research_options["content_type"] = st.multiselect(
"Content Type",
["Blog Posts", "Articles", "Social Media", "Whitepapers", "Tutorials", "Videos"],
default=st.session_state.research_options["content_type"],
help="Select content types to tailor research results"
)
st.session_state.research_options["search_depth"] = st.slider(
"Search Depth",
min_value=1,
max_value=5,
value=st.session_state.research_options["search_depth"],
help="Higher depth means more comprehensive but slower research"
)
with tab2:
col1, col2 = st.columns(2)
with col1:
st.session_state.research_options["geo_location"] = st.selectbox(
"Geographic Location",
options=["us", "in", "uk", "fr", "de", "jp", "custom"],
index=["us", "in", "uk", "fr", "de", "jp"].index(st.session_state.research_options["geo_location"]),
help="Target specific geographic region for research"
)
st.session_state.research_options["num_results"] = st.number_input(
"Number of Results",
min_value=1,
max_value=100,
value=st.session_state.research_options["num_results"],
help="Number of results to analyze"
)
with col2:
st.session_state.research_options["search_language"] = st.selectbox(
"Search Language",
options=["en", "hi", "fr", "de", "es", "custom"],
index=["en", "hi", "fr", "de", "es"].index(st.session_state.research_options["search_language"]),
help="Primary language for search results"
)
st.session_state.research_options["time_range"] = st.selectbox(
"Time Range",
options=["past day", "past week", "past month", "past year", "anytime"],
index=["past day", "past week", "past month", "past year", "anytime"].index(st.session_state.research_options["time_range"]),
help="Time period for research results"
)
# Add the technical options to the Advanced tab
st.markdown("---")
st.markdown("### Advanced Search Parameters")
st.session_state.research_options["include_domains"] = st.text_input(
"Include Domains",
value=st.session_state.research_options["include_domains"],
placeholder="example.com, another.com",
help="Specific domains to include in research"
)
st.session_state.research_options["similar_url"] = st.text_input(
"Similar URL",
value=st.session_state.research_options["similar_url"],
placeholder="https://example.com/page",
help="Find content similar to this URL"
)
col1, col2 = st.columns([1, 1])
with col1:
if st.button("Apply", use_container_width=True, type="primary"):
st.session_state.show_options_dialog = False
st.rerun()
with col2:
if st.button("Cancel", use_container_width=True):
st.session_state.show_options_dialog = False
st.rerun()
# Main interface
st.title("ALwrity Web Researcher")
# Primary search area with help popover
with st.popover(" Keyword Research Tips"):
st.markdown("""
### How to Get Better Results
1. **Primary Keywords**: Your main topic or focus
2. **Related Keywords**: Supporting terms that add context
3. **Search Depth**: Higher depth = more comprehensive but slower
4. **Target Audience**: Affects content recommendations
5. **Content Type**: Influences research focus
6. **Search Mode**: Choose between traditional web research(Google), AI-powered search(Tavily and Metaphor) and Deep Researcher
""")
col1, col2 = st.columns([3, 1])
with col1:
st.session_state.research_options["primary_keywords"] = st.text_input(
"Primary Keywords",
value=st.session_state.research_options["primary_keywords"],
placeholder="Enter main keywords for research...",
help="Enter your main topic or focus keywords"
)
with col2:
if st.button("Research Options", use_container_width=True):
show_research_options()
# Research method selection in main container
st.markdown("### Select Research Method")
search_options = [
("google", "🔍 Google Search", "Traditional web research with AI analysis", bool(api_keys['SERPER_API_KEY'])),
("ai", "🤖 AI Search", "Neural search with semantic analysis", bool(api_keys['METAPHOR_API_KEY'] and api_keys['TAVILY_API_KEY'])),
("deep", "🔬 Deep Search (Beta)", "Advanced deep web analysis", bool(all(api_keys.values())))
]
enabled_options = [opt[1] for opt in search_options if opt[3]]
if enabled_options:
selected_option = st.radio(
"Search Method",
options=enabled_options,
horizontal=True,
help="Choose your preferred research method"
)
# Map the selected option to the search_mode value
for mode, label, _, _ in search_options:
if label == selected_option:
st.session_state.research_options["search_mode"] = mode
break
else:
st.warning("No search methods available. Please configure API keys.")
# Execute search button
if st.button("🔍 Start Research", type="primary", use_container_width=True):
if not st.session_state.research_options["primary_keywords"]:
st.warning("⚠️ Please enter primary keywords for research")
return
try:
# Create compact progress display
progress_container = st.container()
with progress_container:
status_col, progress_col = st.columns([3, 1])
with status_col:
status_display = st.empty()
status_display.info("🚀 Initializing research...")
with progress_col:
progress_bar = st.progress(0)
def update_progress(message, progress=None, level="info"):
"""Update progress bar and status display.
Args:
message (str): The message to display
progress (float, optional): Progress value between 0 and 100. Will be converted to 0.0-1.0
level (str, optional): Message level (info, warning, error, success)
"""
if progress is not None:
# Convert percentage to decimal (0.0-1.0)
progress = float(progress) / 100.0
# Ensure progress stays within bounds
progress = max(0.0, min(1.0, progress))
progress_bar.progress(progress)
if level == "error":
status_display.error(f"🚫 {message}")
elif level == "warning":
status_display.warning(f"⚠️ {message}")
elif level == "success":
status_display.success(f"{message}")
else:
status_display.info(f"🔄 {message}")
logger.debug(f"Progress update [{level}]: {message}")
# Execute search with all parameters
try:
update_progress("Starting search...", 0.25)
logger.info(f"Executing web research with mode: {st.session_state.research_options['search_mode']}")
# Create base parameters
research_params = {
"search_keywords": st.session_state.research_options["primary_keywords"],
"search_mode": st.session_state.research_options["search_mode"],
"related_keywords": st.session_state.research_options["related_keywords"],
"target_audience": st.session_state.research_options["target_audience"],
"content_type": st.session_state.research_options["content_type"],
"search_depth": st.session_state.research_options["search_depth"],
"geo_location": st.session_state.research_options["geo_location"],
"search_language": st.session_state.research_options["search_language"],
"num_results": st.session_state.research_options["num_results"],
"time_range": st.session_state.research_options["time_range"],
"include_domains": st.session_state.research_options["include_domains"],
"similar_url": st.session_state.research_options["similar_url"]
}
# Add UI-specific parameters
research_params.update({
"status_container": status_display,
"update_progress": update_progress
})
# For AI search mode, ensure search_keywords is passed correctly
if st.session_state.research_options["search_mode"] == "ai":
research_params["tavily_params"] = {
"max_results": st.session_state.research_options["num_results"],
"search_depth": "advanced" if st.session_state.research_options["search_depth"] > 2 else "basic",
"time_range": st.session_state.research_options["time_range"],
"include_domains": st.session_state.research_options["include_domains"].split(",") if st.session_state.research_options["include_domains"] else [""]
}
# Pass search_keywords as a positional argument
research_params["tavily_search_keywords"] = st.session_state.research_options["primary_keywords"]
# Execute the research
web_research_result = gpt_web_researcher(**research_params)
if web_research_result:
status_display.success("✨ Research completed!")
# Display results in an organized way
with st.expander("📊 Research Results", expanded=False):
st.write(web_research_result)
else:
st.warning("No results found for your search")
except Exception as e:
error_msg = f"Research failed: {str(e)}"
logger.error(error_msg, exc_info=True)
st.error(f"🚫 Research failed: {error_msg}")
except Exception as e:
logger.error(f"Unexpected error in web research: {e}", exc_info=True)
st.error("🚫 An unexpected error occurred. Please try again.")
except Exception as e:
logger.error(f"Unexpected error in web research: {e}", exc_info=True)
st.error("🚫 An unexpected error occurred. Please try again.")

View File

@@ -0,0 +1,331 @@
import streamlit as st
def apply_navigation_styles():
"""Apply navigation and UI setup specific styling."""
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
/* Main app styling for navigation */
.stApp {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
/* Compact layout styling with zero top padding when sub-tab selected */
.main .block-container {
padding-top: 0 !important; /* Remove all top padding */
padding-bottom: 0;
max-width: 100%;
}
/* Remove extra padding and margins */
.stMarkdown {
margin: 0;
padding: 0;
}
/* Header styling with zero margins when in sub-tab */
.sub-tab-active h1, .sub-tab-active h2, .sub-tab-active h3 {
display: none; /* Hide headers in sub-tab mode */
}
/* Remove extra padding in containers */
.stMarkdown {
margin-bottom: 0;
}
/* Header styling */
h1, h2, h3 {
font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 600;
margin-top: 0;
margin-bottom: 0.5rem; /* Reduced from 1rem */
padding-top: 0;
}
/* Reduce spacing between elements */
.element-container {
margin-bottom: 0.5rem; /* Reduced from 1rem */
}
/* Button styling */
.stButton > button {
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
margin-bottom: 0.25rem; /* Reduced from 0.5rem */
font-family: 'Inter', sans-serif;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* Input field styling */
.stTextInput > div > div > input {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
padding: 0.5rem 1rem;
font-family: 'Inter', sans-serif;
}
/* Checkbox styling */
.stCheckbox > label {
font-weight: 500;
font-family: 'Inter', sans-serif;
}
/* Expander styling */
.streamlit-expanderHeader {
font-weight: 500;
color: #2c3e50;
margin-bottom: 0.5rem;
font-family: 'Inter', sans-serif;
}
/* Success message styling */
.stSuccess {
background: linear-gradient(135deg, #43c6ac 0%, #191654 100%);
padding: 0.75rem;
border-radius: 8px;
color: white;
margin-bottom: 1rem;
font-family: 'Inter', sans-serif;
}
/* Error message styling */
.stError {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
padding: 0.75rem;
border-radius: 8px;
color: white;
margin-bottom: 1rem;
font-family: 'Inter', sans-serif;
}
/* Info message styling */
.stInfo {
padding: 0.75rem;
margin-bottom: 1rem;
font-family: 'Inter', sans-serif;
}
/* Sidebar navigation styling */
.sidebar-nav {
padding: 0.5rem 0;
}
.nav-button {
width: 100%;
text-align: left;
padding: 0.5rem 1rem;
background: transparent;
border: none;
color: #2c3e50;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
margin: 0.2rem 0;
border-radius: 4px;
font-family: 'Inter', sans-serif;
}
.nav-button:hover {
background: rgba(0,0,0,0.05);
padding-left: 0.5rem;
}
.nav-button.active {
background: #1565C0;
color: white;
}
/* Enhanced Sub-menu styling with minimal spacing */
.sub-menu {
padding-left: 1rem;
margin: 0;
border-left: 2px solid rgba(21, 101, 192, 0.3);
background: rgba(255, 255, 255, 0.05);
border-radius: 0 8px 8px 0;
padding-top: 0;
padding-bottom: 0;
}
/* Sub-menu button styling with minimal gaps */
.sub-menu .stButton > button {
font-size: 0.9rem;
text-align: left;
padding: 0.4rem 0.8rem;
background: transparent;
border: none;
color: #2c3e50;
font-weight: 500;
transition: all 0.2s ease;
margin: 0;
border-radius: 4px;
min-height: 0;
height: auto;
line-height: 1.2;
width: 100%;
font-family: 'Inter', sans-serif;
}
/* Platform-specific button styles */
.facebook-button .stButton > button {
color: #4267B2;
background: rgba(66, 103, 178, 0.1);
}
.linkedin-button .stButton > button {
color: #0077B5;
background: rgba(0, 119, 181, 0.1);
}
.twitter-button .stButton > button {
color: #1DA1F2;
background: rgba(29, 161, 242, 0.1);
}
.instagram-button .stButton > button {
color: #E1306C;
background: rgba(225, 48, 108, 0.1);
}
.youtube-button .stButton > button {
color: #FF0000;
background: rgba(255, 0, 0, 0.1);
}
/* Platform-specific hover states */
.facebook-button .stButton > button:hover {
background: rgba(66, 103, 178, 0.2) !important;
color: #4267B2 !important;
}
.linkedin-button .stButton > button:hover {
background: rgba(0, 119, 181, 0.2) !important;
color: #0077B5 !important;
}
.twitter-button .stButton > button:hover {
background: rgba(29, 161, 242, 0.2) !important;
color: #1DA1F2 !important;
}
.instagram-button .stButton > button:hover {
background: rgba(225, 48, 108, 0.2) !important;
color: #E1306C !important;
}
.youtube-button .stButton > button:hover {
background: rgba(255, 0, 0, 0.2) !important;
color: #FF0000 !important;
}
/* Platform-specific active states */
.facebook-button.active .stButton > button {
background: #4267B2 !important;
color: white !important;
}
.linkedin-button.active .stButton > button {
background: #0077B5 !important;
color: white !important;
}
.twitter-button.active .stButton > button {
background: #1DA1F2 !important;
color: white !important;
}
.instagram-button.active .stButton > button {
background: #E1306C !important;
color: white !important;
}
.youtube-button.active .stButton > button {
background: #FF0000 !important;
color: white !important;
}
/* Remove any extra spacing from button containers */
.sub-menu .stButton {
margin: 0;
padding: 0;
}
.sub-menu > div {
margin: 0;
padding: 0;
}
.sub-menu .element-container {
margin: 0;
padding: 0;
}
/* Ensure minimal gaps between elements */
.sub-menu > div:not(:last-child) {
margin-bottom: 1px;
}
/* Sidebar icon styling */
.sidebar-icon {
padding: 1rem;
text-align: center;
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.sidebar-icon img {
width: 80px !important;
height: auto !important;
margin: 0 auto;
}
/* Additional compact layout overrides for specific pages */
.compact-layout .main .block-container {
padding-top: 0.25rem !important;
padding-bottom: 0;
}
/* Hide Streamlit elements for cleaner navigation */
.stApp > header {
visibility: hidden;
}
/* Custom scrollbar for navigation */
.sidebar .stMarkdown::-webkit-scrollbar {
width: 6px;
}
.sidebar .stMarkdown::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.sidebar .stMarkdown::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 3px;
}
.sidebar .stMarkdown::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.5);
}
</style>
""", unsafe_allow_html=True)
def apply_compact_layout():
"""Apply compact layout styling for specific pages."""
st.markdown("""
<style>
.main .block-container {
padding-top: 0.25rem !important;
padding-bottom: 0;
}
</style>
""", unsafe_allow_html=True)

View File

@@ -0,0 +1,705 @@
import streamlit as st
from loguru import logger
from typing import List, Dict, Any, Callable
# Import existing tools
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
from lib.ai_seo_tools.content_title_generator import ai_title_generator
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
from lib.ai_seo_tools.image_alt_text_generator import alt_text_gen
from lib.ai_seo_tools.opengraph_generator import og_tag_generator
from lib.ai_seo_tools.optimize_images_for_upload import main_img_optimizer
from lib.ai_seo_tools.google_pagespeed_insights import google_pagespeed_insights
from lib.ai_seo_tools.on_page_seo_analyzer import analyze_onpage_seo
from lib.ai_seo_tools.weburl_seo_checker import url_seo_checker
from lib.ai_marketing_tools.ai_backlinker.backlinking_ui_streamlit import backlinking_ui
from lib.ai_seo_tools.content_gap_analysis.ui import ContentGapAnalysisUI
from lib.ai_seo_tools.content_gap_analysis.enhanced_ui import render_enhanced_content_gap_analysis
from lib.ai_seo_tools.content_calendar.ui.dashboard import ContentCalendarDashboard
from lib.ai_seo_tools.technical_seo_crawler import render_technical_seo_crawler
# Import additional tools
from lib.ai_seo_tools.twitter_tags_generator import display_app as twitter_tags_app
from lib.ai_seo_tools.sitemap_analysis import main as sitemap_analyzer
from lib.ai_seo_tools.textstaty import analyze_text as readability_analyzer
from lib.ai_seo_tools.wordcloud import generate_wordcloud
# Import new enterprise tools
from ..ai_seo_tools.google_search_console_integration import render_gsc_integration
from ..ai_seo_tools.ai_content_strategy import render_ai_content_strategy
from ..ai_seo_tools.enterprise_seo_suite import render_enterprise_seo_suite
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header, render_category_header, render_card
# ============================================================================
# TOOL CONFIGURATION FUNCTIONS
# ============================================================================
def get_enterprise_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for enterprise tools."""
return [
{
'name': '🎯 Enterprise SEO Suite',
'description': 'Unified command center for comprehensive SEO management with AI-powered workflows',
'function': render_enterprise_seo_suite,
'features': ['Complete SEO audit workflows', 'AI-powered recommendations', 'Strategic planning', 'Performance tracking']
},
{
'name': '📊 Google Search Console Intelligence',
'description': 'AI-powered insights from Google Search Console data with content recommendations',
'function': render_gsc_integration,
'features': ['GSC data analysis', 'Content opportunities', 'Performance insights', 'Strategic recommendations']
},
{
'name': '🧠 AI Content Strategy Generator',
'description': 'Generate comprehensive content strategies using AI market intelligence',
'function': render_ai_content_strategy,
'features': ['Content pillar development', 'Topic cluster strategy', 'Content calendar planning', 'Distribution strategy']
}
]
def get_analytics_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for analytics tools."""
return [
{
'name': '📊 Google Search Console Intelligence',
'description': 'Deep analysis of GSC data with AI-powered content recommendations',
'function': render_gsc_integration,
'category': 'Search Analytics'
},
{
'name': '🔍 Enhanced Content Gap Analysis',
'description': 'Advanced competitor content analysis with AI insights',
'function': lambda: render_enhanced_content_gap_analysis(),
'category': 'Competitive Intelligence'
},
{
'name': '📈 SEO Performance Tracker',
'description': 'Track and analyze SEO performance with trend analysis',
'function': lambda: st.info("SEO Performance Tracker - Coming soon with advanced metrics"),
'category': 'Performance Analytics'
}
]
def get_technical_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for technical SEO tools."""
return [
{
'name': '🔍 Technical SEO Crawler',
'description': 'Comprehensive site-wide technical SEO analysis',
'function': lambda: render_technical_seo_crawler(),
'priority': 'High'
},
{
'name': '📱 Mobile SEO Analyzer',
'description': 'Mobile-specific SEO analysis and optimization',
'function': lambda: st.info("Mobile SEO Analyzer - Advanced mobile optimization coming soon"),
'priority': 'Medium'
},
{
'name': '⚡ Core Web Vitals Optimizer',
'description': 'Analyze and optimize Core Web Vitals performance',
'function': lambda: st.info("Core Web Vitals Optimizer - Performance optimization coming soon"),
'priority': 'High'
},
{
'name': '🗺️ XML Sitemap Generator',
'description': 'Generate and optimize XML sitemaps',
'function': lambda: st.info("XML Sitemap Generator - Coming soon"),
'priority': 'Medium'
}
]
def get_content_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for content and strategy tools."""
return [
{
'name': '🧠 AI Content Strategy Generator',
'description': 'Comprehensive content strategy with AI market intelligence',
'function': render_ai_content_strategy,
'type': 'Enterprise'
},
{
'name': '📅 Content Calendar Planner',
'description': 'AI-powered content calendar with SEO optimization',
'function': lambda: render_content_calendar(),
'type': 'Professional'
},
{
'name': '🎯 Topic Cluster Generator',
'description': 'Generate SEO topic clusters for content dominance',
'function': lambda: st.info("Topic Cluster Generator - Advanced clustering coming soon"),
'type': 'Professional'
},
{
'name': '📊 Content Performance Analyzer',
'description': 'Analyze content performance and optimization opportunities',
'function': lambda: st.info("Content Performance Analyzer - Coming soon"),
'type': 'Standard'
}
]
def get_basic_tools_config() -> List[Dict[str, Any]]:
"""Get configuration for basic SEO tools."""
return [
{
'name': '📝 Meta Description Generator',
'description': 'Generate SEO-optimized meta descriptions',
'function': lambda: metadesc_generator_main(),
'category': 'Metadata'
},
{
'name': '🎯 Content Title Generator',
'description': 'Create compelling, SEO-friendly titles',
'function': lambda: ai_title_generator(),
'category': 'Content'
},
{
'name': '🔗 OpenGraph Generator',
'description': 'Generate social media OpenGraph tags',
'function': lambda: og_tag_generator(),
'category': 'Social'
},
{
'name': '🖼️ Image Alt Text Generator',
'description': 'Generate SEO-friendly image alt text',
'function': lambda: alt_text_gen(),
'category': 'Images'
},
{
'name': '📋 Schema Markup Generator',
'description': 'Generate structured data markup',
'function': lambda: ai_structured_data(),
'category': 'Technical'
},
{
'name': '🔍 On-Page SEO Analyzer',
'description': 'Comprehensive on-page SEO analysis',
'function': lambda: analyze_onpage_seo(),
'category': 'Analysis'
},
{
'name': '🌐 URL SEO Checker',
'description': 'Quick SEO check for any URL',
'function': lambda: url_seo_checker(),
'category': 'Analysis'
}
]
def get_tool_functions_mapping() -> Dict[str, Callable]:
"""Get mapping of tool names to their functions for URL routing."""
return {
# Core content tools
"structured_data": ai_structured_data,
"blog_title": ai_title_generator,
"meta_description": metadesc_generator_main,
"alt_text": alt_text_gen,
"opengraph": og_tag_generator,
"image_optimizer": main_img_optimizer,
# Technical analysis tools
"technical_seo_crawler": render_technical_seo_crawler,
"pagespeed": google_pagespeed_insights,
"onpage_seo": analyze_onpage_seo,
"url_checker": url_seo_checker,
"sitemap_analysis": sitemap_analyzer,
# Social media tools
"twitter_tags": render_twitter_tags,
# Content analysis tools
"readability_analyzer": render_readability_analyzer,
"wordcloud_generator": render_wordcloud_generator,
# Advanced tools
"backlinking": backlinking_ui,
"content_gap_analysis": render_content_gap_analysis,
"enhanced_content_gap_analysis": render_enhanced_content_gap_analysis_ui,
"content_calendar": render_content_calendar,
# Tool combinations for workflow efficiency
"content_optimization": lambda: run_tool_combination([
ai_title_generator,
metadesc_generator_main,
ai_structured_data
], "Content Optimization Suite"),
"technical_audit": lambda: run_tool_combination([
google_pagespeed_insights,
analyze_onpage_seo,
url_seo_checker
], "Technical SEO Audit"),
"image_optimization": lambda: run_tool_combination([
alt_text_gen,
main_img_optimizer
], "Image Optimization Suite"),
"social_optimization": lambda: run_tool_combination([
og_tag_generator,
render_twitter_tags
], "Social Media Optimization")
}
# ============================================================================
# INDIVIDUAL TOOL RENDERING FUNCTIONS
# ============================================================================
def render_content_gap_analysis():
"""Render the content gap analysis workflow interface."""
ui = ContentGapAnalysisUI()
ui.run()
def render_enhanced_content_gap_analysis_ui():
"""Render the enhanced content gap analysis with advertools integration."""
render_enhanced_content_gap_analysis()
def render_content_calendar():
"""Render the content calendar dashboard with proper error handling."""
import logging
import sys
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('content_calendar.log', mode='a')
]
)
calendar_logger = logging.getLogger('content_calendar')
try:
calendar_logger.info("Initializing Content Calendar Dashboard")
dashboard = ContentCalendarDashboard()
calendar_logger.info("Rendering Content Calendar Dashboard")
dashboard.render()
calendar_logger.info("Content Calendar Dashboard rendered successfully")
except Exception as e:
calendar_logger.error(f"Error rendering content calendar: {str(e)}", exc_info=True)
st.error(f"An error occurred while loading the content calendar: {str(e)}")
def render_twitter_tags():
"""Render the Twitter tags generator."""
twitter_tags_app()
def render_readability_analyzer():
"""Render the text readability analyzer."""
st.title("📖 Text Readability Analyzer")
st.write("Making Your Content Easy to Read")
text_input = st.text_area("Paste your text here:", height=200)
if st.button("Analyze Readability"):
if text_input.strip():
_display_readability_metrics(text_input)
_display_readability_recommendations()
else:
st.error("Please enter text to analyze.")
def _display_readability_metrics(text: str):
"""Display readability metrics for the given text."""
from textstat import textstat
metrics = {
"Flesch Reading Ease": textstat.flesch_reading_ease(text),
"Flesch-Kincaid Grade Level": textstat.flesch_kincaid_grade(text),
"Gunning Fog Index": textstat.gunning_fog(text),
"SMOG Index": textstat.smog_index(text),
"Automated Readability Index": textstat.automated_readability_index(text),
"Coleman-Liau Index": textstat.coleman_liau_index(text),
"Linsear Write Formula": textstat.linsear_write_formula(text),
"Dale-Chall Readability Score": textstat.dale_chall_readability_score(text),
"Readability Consensus": textstat.readability_consensus(text)
}
st.subheader("Text Analysis Results")
for metric, value in metrics.items():
st.metric(metric, f"{value:.2f}")
def _display_readability_recommendations():
"""Display readability recommendations."""
st.subheader("Key Takeaways:")
st.markdown("""
* **Don't Be Afraid to Simplify!** Often, simpler language makes content more impactful and easier to digest.
* **Aim for a Reading Level Appropriate for Your Audience:** Consider the education level, background, and familiarity of your readers.
* **Use Short Sentences:** This makes your content more scannable and easier to read.
* **Write for Everyone:** Accessibility should always be a priority. When in doubt, aim for clear, concise language!
""")
def render_wordcloud_generator():
"""Render the word cloud generator."""
st.title("☁️ Word Cloud Generator")
st.write("Visualize the most important words in your content")
text_input = st.text_area("Enter your text:", height=200)
if st.button("Generate Word Cloud"):
if text_input.strip():
_generate_and_display_wordcloud(text_input)
_display_text_statistics(text_input)
else:
st.error("Please enter text to generate a word cloud.")
def _generate_and_display_wordcloud(text: str):
"""Generate and display word cloud for the given text."""
from wordcloud import WordCloud
import matplotlib.pyplot as plt
# Create and generate a word cloud image
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(text)
# Display the word cloud
st.subheader("Word Cloud Visualization")
fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis('off')
st.pyplot(fig)
def _display_text_statistics(text: str):
"""Display basic text statistics."""
st.subheader("Text Statistics")
words = text.split()
unique_words = set(words)
st.metric("Total Words", len(words))
st.metric("Unique Words", len(unique_words))
# ============================================================================
# TAB RENDERING FUNCTIONS
# ============================================================================
def render_enterprise_tab():
"""Render the Enterprise Suite tab."""
st.header("🏢 Enterprise SEO Command Center")
st.markdown("**Unified SEO management for enterprise-level optimization**")
enterprise_tools = get_enterprise_tools_config()
# Display enterprise tools
for tool in enterprise_tools:
_render_enterprise_tool_card(tool)
# Render selected enterprise tool
_render_selected_enterprise_tool(enterprise_tools)
def _render_enterprise_tool_card(tool: Dict[str, Any]):
"""Render an individual enterprise tool card."""
with st.expander(f"{tool['name']} - {tool['description']}", expanded=False):
col1, col2 = st.columns([2, 1])
with col1:
st.markdown("**Key Features:**")
for feature in tool['features']:
st.write(f"{feature}")
with col2:
if st.button(f"Launch {tool['name'].split()[1]}", key=f"enterprise_{tool['name']}", use_container_width=True):
st.session_state.selected_enterprise_tool = tool['name']
tool['function']()
def _render_selected_enterprise_tool(enterprise_tools: List[Dict[str, Any]]):
"""Render the selected enterprise tool if any."""
if 'selected_enterprise_tool' in st.session_state:
selected_tool = next((tool for tool in enterprise_tools if tool['name'] == st.session_state.selected_enterprise_tool), None)
if selected_tool:
st.markdown("---")
selected_tool['function']()
def render_analytics_tab():
"""Render the Analytics & Intelligence tab."""
st.header("📊 Analytics & Intelligence")
st.markdown("**Advanced analytics and competitive intelligence tools**")
analytics_tools = get_analytics_tools_config()
# Group tools by category
categories = _group_tools_by_category(analytics_tools)
for category, tools in categories.items():
st.subheader(f"📊 {category}")
for tool in tools:
_render_analytics_tool_row(tool)
def _group_tools_by_category(tools: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""Group tools by their category."""
categories = {}
for tool in tools:
category = tool['category']
if category not in categories:
categories[category] = []
categories[category].append(tool)
return categories
def _render_analytics_tool_row(tool: Dict[str, Any]):
"""Render an analytics tool row."""
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{tool['name']}**")
st.write(tool['description'])
with col2:
if st.button("Launch", key=f"analytics_{tool['name']}", use_container_width=True):
tool['function']()
def render_technical_tab():
"""Render the Technical SEO tab."""
st.header("🔧 Technical SEO")
st.markdown("**Advanced technical SEO analysis and optimization tools**")
technical_tools = get_technical_tools_config()
# Display technical tools with priority indicators
for tool in technical_tools:
_render_technical_tool_row(tool)
def _render_technical_tool_row(tool: Dict[str, Any]):
"""Render a technical tool row with priority indicator."""
priority_color = "🔴" if tool['priority'] == 'High' else "🟡"
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
st.markdown(f"**{tool['name']}** {priority_color}")
st.write(tool['description'])
with col2:
st.write(f"**Priority:** {tool['priority']}")
with col3:
if st.button("Launch", key=f"technical_{tool['name']}", use_container_width=True):
tool['function']()
def render_content_tab():
"""Render the Content & Strategy tab."""
st.header("📝 Content & Strategy")
st.markdown("**AI-powered content creation and strategy tools**")
content_tools = get_content_tools_config()
# Group by tool type
tool_types = _group_tools_by_type(content_tools)
for tool_type, tools in tool_types.items():
_render_content_tool_section(tool_type, tools)
def _group_tools_by_type(tools: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""Group tools by their type."""
tool_types = {}
for tool in tools:
tool_type = tool['type']
if tool_type not in tool_types:
tool_types[tool_type] = []
tool_types[tool_type].append(tool)
return tool_types
def _render_content_tool_section(tool_type: str, tools: List[Dict[str, Any]]):
"""Render a content tool section."""
type_color = {"Enterprise": "🏢", "Professional": "💼", "Standard": "📋"}
st.subheader(f"{type_color.get(tool_type, '📋')} {tool_type} Tools")
for tool in tools:
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{tool['name']}**")
st.write(tool['description'])
with col2:
if st.button("Launch", key=f"content_{tool['name']}", use_container_width=True):
tool['function']()
def render_basic_tools_tab():
"""Render the Basic Tools tab."""
st.header("🎯 Basic SEO Tools")
st.markdown("**Essential SEO tools for quick optimization tasks**")
basic_tools = get_basic_tools_config()
# Group basic tools by category
basic_categories = _group_tools_by_category(basic_tools)
# Display in columns for better layout
_render_basic_tools_in_columns(basic_categories)
def _render_basic_tools_in_columns(basic_categories: Dict[str, List[Dict[str, Any]]]):
"""Render basic tools in two columns."""
col1, col2 = st.columns(2)
categories_list = list(basic_categories.items())
mid_point = len(categories_list) // 2
with col1:
for category, tools in categories_list[:mid_point]:
_render_basic_tool_category(category, tools)
with col2:
for category, tools in categories_list[mid_point:]:
_render_basic_tool_category(category, tools)
def _render_basic_tool_category(category: str, tools: List[Dict[str, Any]]):
"""Render a basic tool category."""
st.subheader(f"📂 {category}")
for tool in tools:
if st.button(f"{tool['name']}", key=f"basic_{tool['name']}", use_container_width=True):
tool['function']()
st.caption(tool['description'])
st.markdown("---")
def render_enterprise_features_footer():
"""Render the enterprise features footer."""
st.markdown("---")
st.markdown("### 🚀 Enterprise SEO Features")
col1, col2, col3 = st.columns(3)
with col1:
st.info("""
**🏢 Enterprise Suite**
- Unified SEO workflows
- AI-powered insights
- Strategic planning
- Performance tracking
""")
with col2:
st.info("""
**📊 Advanced Analytics**
- GSC integration
- Competitive intelligence
- Content gap analysis
- Performance insights
""")
with col3:
st.info("""
**🧠 AI Strategy**
- Content strategy generation
- Topic cluster planning
- Distribution optimization
- Market intelligence
""")
# ============================================================================
# MAIN DASHBOARD FUNCTIONS
# ============================================================================
def render_seo_tools_dashboard():
"""Render comprehensive SEO tools dashboard with enterprise features."""
st.title("🚀 Alwrity AI SEO Tools")
st.markdown("**Enterprise-level SEO tools powered by artificial intelligence**")
# Create tabs for different tool categories
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"🏢 Enterprise Suite",
"📊 Analytics & Intelligence",
"🔧 Technical SEO",
"📝 Content & Strategy",
"🎯 Basic Tools"
])
with tab1:
render_enterprise_tab()
with tab2:
render_analytics_tab()
with tab3:
render_technical_tab()
with tab4:
render_content_tab()
with tab5:
render_basic_tools_tab()
# Add footer with enterprise features highlight
render_enterprise_features_footer()
def ai_seo_tools():
"""Main entry point for SEO tools dashboard with premium glassmorphic design."""
logger.info("Starting SEO Tools Dashboard")
# Apply common dashboard styling
apply_dashboard_style()
# Check if a specific tool is selected
selected_tool = st.query_params.get("tool")
if selected_tool:
_handle_selected_tool(selected_tool)
else:
# Show the dashboard if no tool is selected
render_seo_tools_dashboard()
def _handle_selected_tool(selected_tool: str):
"""Handle rendering of a specific selected tool."""
tool_functions = get_tool_functions_mapping()
if selected_tool in tool_functions:
# Clear any existing content
st.empty()
# Execute the selected tool's function
tool_functions[selected_tool]()
else:
st.error(f"Tool '{selected_tool}' is not available or under development.")
st.info("Please select a different tool from the dashboard.")
render_seo_tools_dashboard()
def run_tool_combination(tools: List[Callable], combination_name: str):
"""Run a combination of tools and provide cross-tool analysis."""
st.markdown(f"# {combination_name}")
st.markdown("Comprehensive SEO analysis workflow")
# Create tabs for each tool in the combination
tab_names = _generate_tab_names(tools)
tabs = st.tabs(tab_names)
# Run each tool in its own tab
_execute_tools_in_tabs(tabs, tools)
# Add cross-tool analysis section
_render_analysis_summary()
def _generate_tab_names(tools: List[Callable]) -> List[str]:
"""Generate tab names for tool combination."""
tab_names = []
for i, tool in enumerate(tools):
if hasattr(tool, '__name__'):
tab_names.append(tool.__name__.replace('_', ' ').title())
else:
tab_names.append(f"Step {i+1}")
return tab_names
def _execute_tools_in_tabs(tabs: List, tools: List[Callable]):
"""Execute tools in their respective tabs."""
for tab, tool in zip(tabs, tools):
with tab:
try:
tool()
except Exception as e:
st.error(f"Error running tool: {str(e)}")
logger.error(f"Error in tool combination: {str(e)}")
def _render_analysis_summary():
"""Render the analysis summary section."""
with st.expander("📊 Analysis Summary", expanded=True):
st.markdown("""
### Key Recommendations:
1. **Content Optimization**: Ensure your titles and meta descriptions are keyword-optimized
2. **Technical Performance**: Address any speed or technical issues identified
3. **Structured Data**: Implement schema markup for better search visibility
4. **Social Optimization**: Optimize social sharing tags for better engagement
### Next Steps:
- Implement the recommendations from each tool
- Monitor your rankings and traffic after changes
- Regularly audit your content using these tools
""")
# Add export functionality placeholder
if st.button("📥 Export Analysis Report", use_container_width=True):
st.info("Export functionality is being developed. Save your results manually for now.")

View File

@@ -0,0 +1,973 @@
import streamlit as st
from loguru import logger
import asyncio
from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService
from lib.personalization.style_analyzer import StyleAnalyzer
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header
import sys
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/settings_page.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
backtrace=True,
diagnose=True
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def display_style_analysis(analysis_results: dict):
"""Display the style analysis results in a structured format with premium styling."""
try:
# Writing Style Section
st.markdown("""
<div class="analysis-section">
<div class="section-icon">🎨</div>
<h3>Writing Style Analysis</h3>
</div>
""", unsafe_allow_html=True)
writing_style = analysis_results.get("writing_style", {})
col1, col2 = st.columns(2)
with col1:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Tone:</span>
<span class="metric-value">{writing_style.get("tone", "N/A")}</span>
</div>
<div class="metric-item">
<span class="metric-label">Voice:</span>
<span class="metric-value">{writing_style.get("voice", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Complexity:</span>
<span class="metric-value">{writing_style.get("complexity", "N/A")}</span>
</div>
<div class="metric-item">
<span class="metric-label">Engagement:</span>
<span class="metric-value">{writing_style.get("engagement_level", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
# Content Characteristics Section
st.markdown("""
<div class="analysis-section">
<div class="section-icon">📊</div>
<h3>Content Characteristics</h3>
</div>
""", unsafe_allow_html=True)
content_chars = analysis_results.get("content_characteristics", {})
col1, col2 = st.columns(2)
with col1:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Sentence Structure:</span>
<span class="metric-value">{content_chars.get("sentence_structure", "N/A")}</span>
</div>
<div class="metric-item">
<span class="metric-label">Vocabulary Level:</span>
<span class="metric-value">{content_chars.get("vocabulary_level", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Organization:</span>
<span class="metric-value">{content_chars.get("paragraph_organization", "N/A")}</span>
</div>
<div class="metric-item">
<span class="metric-label">Content Flow:</span>
<span class="metric-value">{content_chars.get("content_flow", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
# Target Audience Section
st.markdown("""
<div class="analysis-section">
<div class="section-icon">🎯</div>
<h3>Target Audience</h3>
</div>
""", unsafe_allow_html=True)
target_audience = analysis_results.get("target_audience", {})
col1, col2 = st.columns(2)
with col1:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Demographics:</span>
<span class="metric-value">{', '.join(target_audience.get("demographics", ["N/A"]))}</span>
</div>
<div class="metric-item">
<span class="metric-label">Expertise Level:</span>
<span class="metric-value">{target_audience.get("expertise_level", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Industry Focus:</span>
<span class="metric-value">{target_audience.get("industry_focus", "N/A")}</span>
</div>
<div class="metric-item">
<span class="metric-label">Geographic Focus:</span>
<span class="metric-value">{target_audience.get("geographic_focus", "N/A")}</span>
</div>
</div>
""", unsafe_allow_html=True)
# Recommended Settings Section
st.markdown("""
<div class="analysis-section">
<div class="section-icon">⚙️</div>
<h3>Recommended Settings</h3>
</div>
""", unsafe_allow_html=True)
recommended = analysis_results.get("recommended_settings", {})
st.markdown(f"""
<div class="recommendations-grid">
<div class="recommendation-card">
<div class="rec-icon">🎭</div>
<div class="rec-label">Writing Tone</div>
<div class="rec-value">{recommended.get("writing_tone", "N/A")}</div>
</div>
<div class="recommendation-card">
<div class="rec-icon">👥</div>
<div class="rec-label">Target Audience</div>
<div class="rec-value">{recommended.get("target_audience", "N/A")}</div>
</div>
<div class="recommendation-card">
<div class="rec-icon">📝</div>
<div class="rec-label">Content Type</div>
<div class="rec-value">{recommended.get("content_type", "N/A")}</div>
</div>
<div class="recommendation-card">
<div class="rec-icon">🎨</div>
<div class="rec-label">Creativity Level</div>
<div class="rec-value">{recommended.get("creativity_level", "N/A")}</div>
</div>
</div>
""", unsafe_allow_html=True)
except Exception as e:
logger.error(f"Error displaying style analysis: {str(e)}")
st.error(f"Error displaying analysis results: {str(e)}")
def render_settings_page():
"""Renders the settings page with premium glassmorphic design and all configuration options in tabs"""
# Apply common dashboard styling
apply_dashboard_style()
# Add settings-specific CSS for tabs and form elements
st.markdown("""
<style>
/* Settings-specific overrides and additions */
.main .block-container {
padding: 1rem 1.5rem 1.5rem 1.5rem !important;
margin: 1rem auto !important;
}
.element-container {
margin-bottom: 0.3rem !important;
}
.stMarkdown {
margin: 0 !important;
padding: 0 !important;
margin-bottom: 0.3rem !important;
}
/* Enhanced tab styling for settings */
.stTabs [data-baseweb="tab-list"] {
gap: 0.5rem !important;
background: rgba(255, 255, 255, 0.35) !important;
backdrop-filter: blur(30px) !important;
border-radius: 18px !important;
padding: 0.8rem !important;
border: 3px solid rgba(255, 255, 255, 0.4) !important;
margin-bottom: 1.5rem !important;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.25),
inset 0 3px 0 rgba(255, 255, 255, 0.5) !important;
}
.stTabs [data-baseweb="tab"] {
background: rgba(255, 255, 255, 0.3) !important;
backdrop-filter: blur(25px) !important;
border-radius: 14px !important;
color: #ffffff !important;
border: 2px solid rgba(255, 255, 255, 0.35) !important;
padding: 1rem 2rem !important;
font-weight: 800 !important;
font-size: 1.05rem !important;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
margin: 0 !important;
min-height: 50px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
text-shadow: 0 3px 6px rgba(0, 0, 0, 0.6) !important;
box-shadow:
0 8px 25px rgba(0, 0, 0, 0.2),
inset 0 2px 0 rgba(255, 255, 255, 0.4) !important;
font-family: 'Inter', sans-serif !important;
}
.stTabs [data-baseweb="tab"]:hover {
background: rgba(255, 255, 255, 0.4) !important;
backdrop-filter: blur(30px) !important;
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.5) !important;
transform: translateY(-2px) !important;
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.3),
inset 0 3px 0 rgba(255, 255, 255, 0.5) !important;
}
.stTabs [aria-selected="true"] {
background: rgba(255, 255, 255, 0.5) !important;
backdrop-filter: blur(35px) !important;
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.6) !important;
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.3),
inset 0 3px 0 rgba(255, 255, 255, 0.6),
0 0 0 2px rgba(255, 255, 255, 0.3) !important;
transform: translateY(-1px) !important;
font-weight: 900 !important;
text-shadow: 0 3px 8px rgba(0, 0, 0, 0.7) !important;
}
/* Settings sections */
.settings-section {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(25px);
border-radius: 16px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 2px solid rgba(255, 255, 255, 0.25);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
position: relative;
overflow: hidden;
}
.settings-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
opacity: 0.9;
}
.settings-section h2 {
color: #ffffff !important;
font-size: 1.6em !important;
font-weight: 800 !important;
margin-bottom: 1.2rem !important;
margin-top: 0 !important;
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.5) !important;
display: flex;
align-items: center;
gap: 0.6rem;
letter-spacing: -0.01em;
padding-bottom: 0.8rem;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
font-family: 'Inter', sans-serif !important;
}
/* Form elements with maximum visibility */
.stSelectbox > div > div,
.stSelectbox div[data-baseweb="select"] > div {
background: rgba(255, 255, 255, 0.35) !important;
backdrop-filter: blur(30px) !important;
border: 3px solid rgba(255, 255, 255, 0.5) !important;
border-radius: 12px !important;
color: #ffffff !important;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.2),
inset 0 2px 0 rgba(255, 255, 255, 0.4) !important;
min-height: 45px !important;
font-weight: 700 !important;
font-size: 1rem !important;
font-family: 'Inter', sans-serif !important;
}
.stTextInput > div > div > input,
.stTextArea > div > div > textarea,
.stNumberInput > div > div > input {
background: rgba(255, 255, 255, 0.35) !important;
backdrop-filter: blur(30px) !important;
border: 3px solid rgba(255, 255, 255, 0.5) !important;
border-radius: 12px !important;
color: #ffffff !important;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.2),
inset 0 2px 0 rgba(255, 255, 255, 0.4) !important;
min-height: 45px !important;
font-weight: 700 !important;
font-size: 1rem !important;
font-family: 'Inter', sans-serif !important;
}
.stTextInput > div > div > input:focus,
.stTextArea > div > div > textarea:focus,
.stNumberInput > div > div > input:focus {
border-color: rgba(255, 255, 255, 0.7) !important;
box-shadow:
0 0 0 4px rgba(255, 255, 255, 0.4),
0 15px 35px rgba(0, 0, 0, 0.25) !important;
background: rgba(255, 255, 255, 0.4) !important;
}
.stTextInput > div > div > input::placeholder {
color: rgba(255, 255, 255, 0.8) !important;
font-weight: 600 !important;
}
/* Enhanced labels */
.stMarkdown p, .stSelectbox label, .stTextInput label, .stTextArea label, .stSlider label, .stNumberInput label {
color: #ffffff !important;
font-weight: 800 !important;
text-shadow:
0 2px 6px rgba(0, 0, 0, 0.5),
0 1px 3px rgba(0, 0, 0, 0.7) !important;
font-size: 1.05rem !important;
margin-bottom: 0.4rem !important;
font-family: 'Inter', sans-serif !important;
letter-spacing: -0.01em;
}
/* Slider styling */
.stSlider > div > div > div {
background: rgba(255, 255, 255, 0.5) !important;
border-radius: 8px !important;
height: 8px !important;
}
.stSlider > div > div > div > div {
background: #ffffff !important;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25) !important;
width: 20px !important;
height: 20px !important;
}
/* Analysis results styling */
.analysis-section {
display: flex;
align-items: center;
gap: 1rem;
margin: 2rem 0 1rem 0;
padding: 1rem;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
}
.section-icon {
font-size: 1.5em;
color: #ffffff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.analysis-section h3 {
color: #ffffff;
font-size: 1.2em;
font-weight: 600;
margin: 0;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.analysis-card {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.15);
margin-bottom: 1rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.metric-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.8rem;
padding-bottom: 0.8rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.metric-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.metric-label {
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
font-size: 0.9em;
}
.metric-value {
color: #ffffff;
font-weight: 600;
font-size: 0.9em;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.recommendations-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.recommendation-card {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(15px);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.15);
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.recommendation-card:hover {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.12);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.rec-icon {
font-size: 2em;
margin-bottom: 0.5rem;
color: #ffffff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.rec-label {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9em;
font-weight: 500;
margin-bottom: 0.5rem;
}
.rec-value {
color: #ffffff;
font-weight: 600;
font-size: 1em;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
/* Status messages */
.stSuccess {
background: rgba(46, 160, 67, 0.2) !important;
backdrop-filter: blur(15px) !important;
border: 1px solid rgba(46, 160, 67, 0.3) !important;
border-radius: 12px !important;
color: #ffffff !important;
}
.stError {
background: rgba(255, 75, 75, 0.2) !important;
backdrop-filter: blur(15px) !important;
border: 1px solid rgba(255, 75, 75, 0.3) !important;
border-radius: 12px !important;
color: #ffffff !important;
}
.stWarning {
background: rgba(255, 196, 9, 0.2) !important;
backdrop-filter: blur(15px) !important;
border: 1px solid rgba(255, 196, 9, 0.3) !important;
border-radius: 12px !important;
color: #ffffff !important;
}
.stStatus {
background: rgba(255, 255, 255, 0.08) !important;
backdrop-filter: blur(15px) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-radius: 12px !important;
}
</style>
""", unsafe_allow_html=True)
# Use the common dashboard header
render_dashboard_header(
"⚙️ Settings & Configuration",
"Customize your AI experience with precision controls for content generation, personalization, and optimization. Fine-tune every aspect to match your unique requirements and style."
)
# Create tabs for different settings categories with premium styling
tabs = st.tabs([
"📝 Content",
"🖼️ Images",
"🤖 LLM",
"🔍 Search",
"🎨 AI Personalization"
])
# Content Settings Tab
with tabs[0]:
st.markdown("""
<div class="settings-section">
<h2>📝 Content Personalization</h2>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
blog_length = st.text_input(
"**Content Length (words)**",
value="2000",
key="settings_blog_length",
help="Approximate word count for blogs. Note: Actual length may vary based on GPT provider and max token count."
)
blog_tone_options = ["Casual", "Professional", "How-to", "Beginner", "Research", "Programming", "Social Media", "Customize"]
blog_tone = st.selectbox(
"**Content Tone**",
options=blog_tone_options,
key="settings_blog_tone",
help="Select the desired tone for the blog content."
)
# Initialize custom_tone variable
custom_tone = ""
if blog_tone == "Customize":
custom_tone = st.text_input(
"Enter the tone of your content",
key="settings_custom_tone",
help="Specify the tone of your content."
)
if custom_tone:
blog_tone = custom_tone
else:
st.warning("Please specify the tone of your content.")
blog_demographic_options = ["Professional", "Gen-Z", "Tech-savvy", "Student", "Digital Marketing", "Customize"]
blog_demographic = st.selectbox(
"**Target Audience**",
options=blog_demographic_options,
key="settings_blog_demographic",
help="Select the primary audience for the blog content."
)
with col2:
blog_type = st.selectbox(
"**Content Type**",
options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"],
key="settings_blog_type",
help="Select the category that best describes the blog content."
)
blog_language = st.selectbox(
"**Content Language**",
options=["English", "Spanish", "German", "Chinese", "Arabic", "Nepali", "Hindi", "Hindustani", "Customize"],
key="settings_blog_language",
help="Select the language in which the blog will be written."
)
blog_output_format = st.selectbox(
"**Content Output Format**",
options=["markdown", "HTML", "plaintext"],
key="settings_blog_output_format",
help="Select the format for the blog output."
)
# Images Settings Tab
with tabs[1]:
st.markdown("""
<div class="settings-section">
<h2>🖼️ Images Personalization</h2>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
image_generation_model = st.selectbox(
"**Image Generation Model**",
options=["stable-diffusion", "dalle2", "dalle3"],
key="settings_image_model",
help="Select the model to generate images for the blog."
)
with col2:
number_of_blog_images = st.number_input(
"**Number of Blog Images**",
value=1,
min_value=1,
max_value=10,
key="settings_number_of_images",
help="Specify the number of images to include in the blog."
)
# LLM Settings Tab
with tabs[2]:
st.markdown("""
<div class="settings-section">
<h2>🤖 LLM Personalization</h2>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
gpt_provider = st.selectbox(
"**GPT Provider**",
options=["google", "openai", "minstral"],
key="settings_gpt_provider",
help="Select the provider for the GPT model."
)
model = st.text_input(
"**Model**",
value="gemini-1.5-flash-latest",
key="settings_model",
help="Specify the model version to use from the selected provider."
)
temperature = st.slider(
"**Temperature**",
min_value=0.1,
max_value=1.0,
value=0.7,
step=0.1,
key="settings_temperature",
help="Controls the creativity level of the generated text."
)
max_tokens = st.selectbox(
"**Max Tokens**",
options=[500, 1000, 2000, 4000, 16000, 32000, 64000],
index=3,
key="settings_max_tokens",
help="Maximum length of the output sequence."
)
with col2:
top_p = st.slider(
"**Top-p**",
min_value=0.0,
max_value=1.0,
value=0.9,
step=0.1,
key="settings_top_p",
help="Controls diversity in text generation."
)
frequency_penalty = st.slider(
"**Frequency Penalty**",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
key="settings_frequency_penalty",
help="Reduces word repetition in output."
)
# Search Settings Tab
with tabs[3]:
st.markdown("""
<div class="settings-section">
<h2>🔍 Search Engine Personalization</h2>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
geographic_location = st.selectbox(
"**Geographic Location**",
options=["us", "in", "fr", "cn"],
key="settings_geographic_location",
help="Select the geographic location for tailoring search results."
)
search_language = st.selectbox(
"**Search Language**",
options=["en", "zn-cn", "de", "hi"],
key="settings_search_language",
help="Select the language for the search results."
)
number_of_results = st.number_input(
"**Number of Results**",
value=10,
min_value=1,
max_value=20,
key="settings_number_of_results",
help="Specify the number of search results to retrieve."
)
with col2:
time_range = st.selectbox(
"**Time Range**",
options=["anytime", "past day", "past week", "past month", "past year"],
key="settings_time_range",
help="Select the time range for filtering search results."
)
include_domains = st.text_input(
"**Include Domains**",
value="",
key="settings_include_domains",
help="List specific domains to include in search results (comma-separated)."
)
similar_url = st.text_input(
"**Similar URL**",
value="",
key="settings_similar_url",
help="Provide a URL to find similar results."
)
# AI Personalization Tab
with tabs[4]:
st.markdown("""
<div class="settings-section">
<h2>🎨 AI Style Analysis</h2>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="analysis-card" style="margin-bottom: 2rem;">
<p style="color: rgba(255, 255, 255, 0.9); font-size: 1.1em; margin: 0; line-height: 1.6;">
Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations for optimal AI content generation.
</p>
</div>
""", unsafe_allow_html=True)
# Create two columns for the layout
col1, col2 = st.columns([2, 1])
with col1:
# Website URL input
st.markdown("#### 🌐 Website URL Analysis")
url = st.text_input(
"Enter your website URL",
placeholder="https://example.com",
key="settings_website_url",
help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead."
)
# Alternative: Written samples
if not url:
st.markdown("#### 📝 Written Samples")
st.markdown("""
<div class="analysis-card">
<p style="color: rgba(255, 255, 255, 0.9); margin: 0; line-height: 1.6;">
No website URL? No problem! You can provide written samples of your content instead.
Share your best articles, blog posts, or any content that represents your writing style.
</p>
</div>
""", unsafe_allow_html=True)
samples = st.text_area(
"Paste your content samples here",
key="settings_content_samples",
help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style.",
height=200
)
with col2:
st.markdown("#### 🎯 Analysis Features")
st.markdown("""
<div class="analysis-card">
<div style="color: rgba(255, 255, 255, 0.9); line-height: 1.6;">
<p><strong>✨ Writing Style:</strong> Tone, voice, complexity analysis</p>
<p><strong>📊 Content Analysis:</strong> Structure and vocabulary assessment</p>
<p><strong>🎯 Audience Insights:</strong> Target demographic identification</p>
<p><strong>⚙️ AI Recommendations:</strong> Personalized settings optimization</p>
</div>
</div>
""", unsafe_allow_html=True)
# Add spacing between categories
st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
if st.button("🎨 Analyze Writing Style", use_container_width=True, key="settings_analyze_style", type="primary"):
if url:
with st.status("Starting style analysis...", expanded=True) as status:
try:
# Step 1: Initialize crawler
status.update(label="Step 1/4: Initializing web crawler...", state="running")
crawler_service = AsyncWebCrawlerService()
# Step 2: Crawl website
status.update(label="Step 2/4: Crawling website content...", state="running")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(crawler_service.crawl_website(url))
loop.close()
if result.get('success', False):
content = result.get('content', {})
# Step 3: Initialize style analyzer
status.update(label="Step 3/4: Analyzing content style...", state="running")
style_analyzer = StyleAnalyzer()
# Step 4: Perform style analysis
status.update(label="Step 4/4: Generating style recommendations...", state="running")
style_analysis = style_analyzer.analyze_content_style(content)
if style_analysis.get('error'):
status.update(label="Analysis failed", state="error")
st.error(f"Style analysis failed: {style_analysis['error']}")
else:
status.update(label="Analysis complete!", state="complete")
# Display style analysis results
display_style_analysis(style_analysis)
# Display original content in tabs
tab1, tab2, tab3 = st.tabs(["📄 Content", "📋 Metadata", "🔗 Links"])
with tab1:
st.markdown("#### Main Content")
st.markdown(f"""
<div class="analysis-card">
<div style="color: rgba(255, 255, 255, 0.9); line-height: 1.6;">
{content.get('main_content', 'No content found')}
</div>
</div>
""", unsafe_allow_html=True)
with tab2:
st.markdown("#### Website Metadata")
st.markdown(f"""
<div class="analysis-card">
<div class="metric-item">
<span class="metric-label">Title:</span>
<span class="metric-value">{content.get('title', 'No title found')}</span>
</div>
<div class="metric-item">
<span class="metric-label">Description:</span>
<span class="metric-value">{content.get('description', 'No description found')}</span>
</div>
</div>
""", unsafe_allow_html=True)
with tab3:
st.markdown("#### Extracted Links")
links = content.get('links', [])
if links:
for link in links[:10]: # Show first 10 links
st.markdown(f"""
<div class="analysis-card" style="margin-bottom: 0.5rem;">
<a href="{link.get('href', '')}" target="_blank" style="color: rgba(255, 255, 255, 0.9); text-decoration: none;">
{link.get('text', 'No text')[:80]}...
</a>
</div>
""", unsafe_allow_html=True)
else:
st.markdown("No links found in the content.")
else:
status.update(label="Crawling failed", state="error")
st.error("Failed to crawl the website. Please check the URL and try again.")
except Exception as e:
status.update(label="Analysis failed", state="error")
st.error(f"An error occurred during analysis: {str(e)}")
elif samples:
with st.status("Starting style analysis...", expanded=True) as status:
try:
# Initialize style analyzer
status.update(label="Analyzing content style...", state="running")
style_analyzer = StyleAnalyzer()
# Perform style analysis
style_analysis = style_analyzer.analyze_content_style({"main_content": samples})
if style_analysis.get('error'):
status.update(label="Analysis failed", state="error")
st.error(f"Style analysis failed: {style_analysis['error']}")
else:
status.update(label="Analysis complete!", state="complete")
# Display style analysis results
display_style_analysis(style_analysis)
except Exception as e:
status.update(label="Analysis failed", state="error")
st.error(f"An error occurred during analysis: {str(e)}")
else:
st.warning("Please provide either a website URL or content samples to analyze.")
# Save Settings Button with premium styling
st.markdown("<div style='height: 2rem'></div>", unsafe_allow_html=True)
if st.button("💾 Save All Settings", type="primary", use_container_width=True, key="settings_save_button"):
# Save all settings to session state
st.session_state.update({
'blog_length': blog_length,
'blog_tone': blog_tone,
'blog_demographic': blog_demographic,
'blog_type': blog_type,
'blog_language': blog_language,
'blog_output_format': blog_output_format,
'image_generation_model': image_generation_model,
'number_of_blog_images': number_of_blog_images,
'gpt_provider': gpt_provider,
'model': model,
'temperature': temperature,
'top_p': top_p,
'max_tokens': max_tokens,
'frequency_penalty': frequency_penalty,
'geographic_location': geographic_location,
'search_language': search_language,
'number_of_results': number_of_results,
'time_range': time_range,
'include_domains': include_domains,
'similar_url': similar_url
})
st.success("✅ Settings saved successfully! Your preferences have been applied to all AI tools.")
# Show a summary of saved settings
st.markdown("""
<div class="analysis-card" style="margin-top: 1rem;">
<h4 style="color: #ffffff; margin-bottom: 1rem;">📋 Settings Summary</h4>
<div style="color: rgba(255, 255, 255, 0.9); line-height: 1.6;">
<p><strong>Content:</strong> {length} words, {tone} tone, {audience} audience</p>
<p><strong>Images:</strong> {images} images using {model}</p>
<p><strong>AI Model:</strong> {provider} - {ai_model}</p>
<p><strong>Search:</strong> {location} region, {results} results</p>
</div>
</div>
""".format(
length=blog_length,
tone=blog_tone,
audience=blog_demographic,
images=number_of_blog_images,
model=image_generation_model,
provider=gpt_provider,
ai_model=model,
location=geographic_location,
results=number_of_results
), unsafe_allow_html=True)

View File

@@ -0,0 +1,374 @@
import streamlit as st
from lib.ai_web_researcher.metaphor_basic_neural_web_search import metaphor_find_similar
from datetime import datetime, timedelta
import re
import urllib.parse
import logging
# Configure logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Create console handler if it doesn't exist
if not logger.handlers:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
def is_valid_url(url):
"""
Check if the provided string is a valid URL.
Args:
url (str): The URL to validate
Returns:
bool: True if valid, False otherwise
"""
try:
result = urllib.parse.urlparse(url)
is_valid = all([result.scheme, result.netloc])
logger.debug(f"URL validation for {url}: {is_valid}")
return is_valid
except Exception as e:
logger.error(f"URL validation error for {url}: {str(e)}")
return False
def competitor_analysis():
logger.info("Starting competitor analysis")
# Initialize session state for progress bar visibility
if 'show_progress' not in st.session_state:
st.session_state.show_progress = True
logger.debug("Initialized show_progress session state")
st.title("Competitor Analysis")
st.markdown("""**Use Cases:**
- Know similar companies and alternatives for the given URL.
- Write listicles, similar companies, Top tools, alternative-to, similar products, similar websites, etc.
[Read More Here](https://docs.exa.ai/reference/company-analyst)
""")
# URL input with validation
similar_url = st.text_input(
"👋 Enter a single valid URL for web analysis:",
placeholder="https://example.com",
help="Enter a complete URL including http:// or https://"
)
# Validate URL
url_valid = is_valid_url(similar_url) if similar_url else False
if similar_url and not url_valid:
logger.warning(f"Invalid URL provided: {similar_url}")
st.error("⚠️ Please enter a valid URL including http:// or https://")
# Usecase selection with improved help
usecase = st.selectbox(
"Select Usecase",
["similar companies", "listicles", "Top tools", "alternative-to", "similar products", "similar websites"],
help="Choose the type of analysis you want to perform"
)
logger.debug(f"Selected usecase: {usecase}")
# Default summary query based on usecase
default_summary_queries = {
"similar companies": "Find companies similar to this one, focusing on their business model, target audience, and market position",
"listicles": "Find similar listicle articles about this topic, focusing on the structure and content",
"Top tools": "Find top tools similar to this one, focusing on features, pricing, and user reviews",
"alternative-to": "Find alternatives to this product or service, focusing on comparable features and pricing",
"similar products": "Find products similar to this one, focusing on features, specifications, and use cases",
"similar websites": "Find websites similar to this one, focusing on design, content, and functionality"
}
# Advanced options using a modal dialog
show_advanced = st.checkbox("Show Advanced Options", help="Configure additional search parameters")
logger.debug(f"Advanced options shown: {show_advanced}")
# Initialize default values
num_results = 5
time_range = "Anytime"
include_domains = []
exclude_domains = []
include_text = None
exclude_text = None
summary_query = default_summary_queries.get(usecase, "")
# Add custom CSS for card styling
st.markdown("""
<style>
.card {
background: linear-gradient(to right, #f0f8ff, #e6f3ff);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
border-left: 4px solid #4a90e2;
}
.card-title {
color: #2c5282;
font-weight: bold;
margin-bottom: 10px;
}
</style>
""", unsafe_allow_html=True)
# Advanced options section
if show_advanced:
logger.debug("Processing advanced options")
st.markdown("### 🔧 Advanced Search Options")
# Summary query with improved help in a card
st.markdown('<div class="card"><div class="card-title">📝 Summary Query</div></div>', unsafe_allow_html=True)
summary_query = st.text_area(
"Customize the summary query",
value=summary_query,
placeholder="Enter a custom query for summarization based on your usecase",
help="This query will be used to generate summaries of the similar content. Be specific about what you want to know."
)
# Number of results with improved help in a card
st.markdown('<div class="card"><div class="card-title">🔢 Number of Results</div></div>', unsafe_allow_html=True)
num_results = st.slider(
"How many results would you like?",
min_value=1,
max_value=20,
value=5,
step=1,
help="How many similar results would you like to see?"
)
# Progress bar visibility toggle
st.markdown('<div class="card"><div class="card-title">🔄 Progress Display</div></div>', unsafe_allow_html=True)
st.session_state.show_progress = st.toggle(
"Show detailed progress bars",
value=st.session_state.show_progress,
help="Toggle to show or hide detailed progress bars during analysis"
)
# Time range selection with improved styling in a card
st.markdown('<div class="card"><div class="card-title">⏱️ Time Range</div></div>', unsafe_allow_html=True)
time_range = st.radio(
"Select time range for results",
options=["Past Week", "Past Month", "Past Year", "Anytime"],
index=3,
horizontal=True,
help="Filter results by when they were published"
)
# Domain filters with improved styling in a card
st.markdown('<div class="card"><div class="card-title">🌐 Domain Filters</div></div>', unsafe_allow_html=True)
domain_filter_type = st.radio(
"Domain Filter Type",
options=["Include Domains", "Exclude Domains", "None"],
index=2,
horizontal=True,
help="Include or exclude specific domains from search results"
)
if domain_filter_type == "Include Domains":
include_domains_input = st.text_input(
"Include Domains (comma-separated)",
placeholder="example.com, another-example.com",
help="Only results from these domains will be included. Example: arxiv.org, paperswithcode.com"
)
if include_domains_input:
include_domains = [domain.strip() for domain in include_domains_input.split(",")]
elif domain_filter_type == "Exclude Domains":
exclude_domains_input = st.text_input(
"Exclude Domains (comma-separated)",
placeholder="example.com, another-example.com",
help="Results from these domains will be excluded from search results"
)
if exclude_domains_input:
exclude_domains = [domain.strip() for domain in exclude_domains_input.split(",")]
# Text filters with improved styling in a card
st.markdown('<div class="card"><div class="card-title">📝 Text Filters</div></div>', unsafe_allow_html=True)
text_filter_type = st.radio(
"Text Filter Type",
options=["Include Text", "Exclude Text", "None"],
index=2,
horizontal=True,
help="Include or exclude results containing specific text"
)
if text_filter_type == "Include Text":
include_text = st.text_input(
"Include Text",
placeholder="large language model",
help="Only results containing this phrase will be included (up to 5 words)"
)
elif text_filter_type == "Exclude Text":
exclude_text = st.text_input(
"Exclude Text",
placeholder="course",
help="Results containing this phrase will be excluded (up to 5 words)"
)
# Analyze button with validation
if st.button("Analyze", disabled=not url_valid if similar_url else False):
if similar_url and url_valid:
try:
logger.info(f"Starting analysis for URL: {similar_url}")
logger.debug(f"Analysis parameters - Usecase: {usecase}, Num Results: {num_results}, Time Range: {time_range}")
# Create a progress container
progress_container = st.empty()
status_container = st.empty()
results_container = st.empty()
# Display initial status
status_container.info(f"Starting analysis for the URL: {similar_url}")
# Create a progress bar
progress_bar = progress_container.progress(0.1)
logger.debug("Initialized progress bar and status containers")
# Calculate date range based on selection
start_date = None
end_date = None
if time_range != "Anytime":
end_date = datetime.now()
if time_range == "Past Week":
start_date = end_date - timedelta(days=7)
elif time_range == "Past Month":
start_date = end_date - timedelta(days=30)
elif time_range == "Past Year":
start_date = end_date - timedelta(days=365)
logger.debug(f"Date range: {start_date} to {end_date}")
# Format dates for API if they exist
start_published_date = start_date.strftime("%Y-%m-%dT%H:%M:%S.000Z") if start_date else None
end_published_date = end_date.strftime("%Y-%m-%dT%H:%M:%S.999Z") if end_date else None
# Prepare summary query
summary_query_param = None
if summary_query:
summary_query_param = {"query": summary_query}
logger.debug(f"Summary query: {summary_query}")
# Update progress
progress_bar.progress(0.2)
status_container.info("Searching for similar content...")
logger.info("Initiating similar content search")
# Call the metaphor_find_similar function with all parameters
with st.spinner("Performing competitor analysis..."):
logger.debug("Calling metaphor_find_similar API")
try:
df, search_response = metaphor_find_similar(
similar_url=similar_url,
usecase=usecase,
num_results=num_results,
start_published_date=start_published_date,
end_published_date=end_published_date,
include_domains=include_domains,
exclude_domains=exclude_domains,
include_text=include_text,
exclude_text=exclude_text,
summary_query=summary_query_param
)
logger.info(f"API call successful. Found {len(df) if not df.empty else 0} results")
# Update progress
progress_bar.progress(0.7)
status_container.info("Processing and analyzing results...")
logger.debug("Processing search results")
# Update progress to complete
progress_bar.progress(1.0)
status_container.success("Analysis completed successfully!")
logger.info("Analysis completed successfully")
except Exception as api_error:
logger.error(f"API call failed: {str(api_error)}", exc_info=True)
raise
# Display results
if not df.empty:
logger.debug(f"Displaying {len(df)} results")
st.subheader("📊 Competitor Analysis Results")
# Add a download button for the results
csv = df.to_csv(index=False)
st.download_button(
label="📥 Download Results as CSV",
data=csv,
file_name=f"competitor_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv",
)
# Display the data editor
st.data_editor(
df,
column_config={
"Title": st.column_config.TextColumn(
"Title",
help="Title of the similar content",
width="large",
),
"URL": st.column_config.LinkColumn(
"URL",
help="Link to the similar content",
width="medium",
display_text="Visit Website",
),
"Content Summary": st.column_config.TextColumn(
"Content Summary",
help="Summary of the similar content",
width="large",
),
},
hide_index=True,
use_container_width=True,
)
# Display additional insights
st.subheader("🔍 Analysis Insights")
# Create columns for metrics
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Results", len(df))
with col2:
# Calculate average content length
avg_content_length = df["Content Summary"].str.len().mean()
st.metric("Avg. Content Length", f"{avg_content_length:.0f} chars")
with col3:
# Calculate unique domains
unique_domains = len(set([url.split('/')[2] for url in df["URL"]]))
st.metric("Unique Domains", unique_domains)
# Display full summaries in expanders
st.subheader("📝 Detailed Competitor Summaries")
if 'competitor_summaries' in st.session_state and st.session_state.competitor_summaries:
for url, data in st.session_state.competitor_summaries.items():
with st.expander(f"📊 {data['title']}", expanded=False):
st.markdown("### 📝 Detailed Competitor Analysis")
st.markdown(data['summary'])
# Display raw data in an expander
with st.expander("View Raw Data"):
st.json(search_response)
else:
logger.warning("No results found for the given URL and parameters")
st.warning("No results found for the given URL and parameters.")
except Exception as err:
logger.error(f"Analysis failed: {str(err)}", exc_info=True)
st.error(f"✖ 🚫 Failed to do similar search.\nError: {err}")
else:
logger.warning("Analysis attempted without valid URL")
st.error("Please enter a valid URL.")

View File

@@ -0,0 +1,116 @@
import streamlit as st
from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header, render_card
from loguru import logger
def render_social_tools_dashboard():
"""Render the social media tools dashboard with premium glassmorphic design."""
logger.info("Starting Social Media Tools Dashboard")
# Apply common dashboard styling
apply_dashboard_style()
# Render dashboard header
render_dashboard_header(
"📱 AI Social Media Tools",
"Create engaging social media content across all major platforms with our specialized AI writers. From viral posts to professional updates, we've got you covered."
)
# Define social tools with enhanced details and platform-specific styling
social_tools = {
"Facebook": {
"icon": "📘",
"description": "Create engaging Facebook posts, stories, and ads that drive meaningful interactions and build community",
"category": "Social Network",
"path": "facebook",
"features": ["Post Generation", "Story Creation", "Ad Copy", "Community Management"]
},
"LinkedIn": {
"icon": "💼",
"description": "Generate professional LinkedIn content, articles, and networking posts that enhance your career presence",
"category": "Professional",
"path": "linkedin",
"features": ["Professional Posts", "Article Writing", "Network Building", "Career Content"]
},
"Twitter": {
"icon": "🐦",
"description": "Craft viral tweets, threads, and engaging content that sparks conversations and grows your following",
"category": "Microblogging",
"path": "twitter",
"features": ["Tweet Generation", "Thread Creation", "Hashtag Strategy", "Viral Content"]
},
"Instagram": {
"icon": "📸",
"description": "Create captivating Instagram captions, stories, and content that showcases your brand beautifully",
"category": "Visual Content",
"path": "instagram",
"features": ["Caption Writing", "Story Content", "Hashtag Research", "Visual Strategy"]
},
"YouTube": {
"icon": "🎥",
"description": "Generate compelling video scripts, descriptions, and content strategies for your YouTube channel",
"category": "Video Content",
"path": "youtube",
"features": ["Script Writing", "Video Descriptions", "SEO Optimization", "Content Planning"]
}
}
# Create a responsive grid of premium cards
cols = st.columns(3)
for idx, (platform, details) in enumerate(social_tools.items()):
with cols[idx % 3]:
# Use the common card renderer
if render_card(
icon=details['icon'],
title=platform,
description=details['description'],
category=details['category'],
key_suffix=f"social_{platform}",
help_text=f"Open {platform} content creation tools - {details['description'][:50]}..."
):
# Set query parameters to redirect to the specific tool
st.query_params["tool"] = details["path"]
st.rerun()
# Add feature showcase section
st.markdown("""
<div style="margin-top: 3rem;">
<div class="dashboard-header" style="margin-bottom: 2rem;">
<h1 style="font-size: 2.2em;">✨ Platform Features</h1>
<p>Each platform comes with specialized AI tools designed for optimal engagement and growth.</p>
</div>
</div>
""", unsafe_allow_html=True)
# Feature grid
feature_cols = st.columns(2)
features = [
{
"title": "🎯 Smart Content Generation",
"description": "AI-powered content creation tailored to each platform's unique audience and format requirements."
},
{
"title": "📊 Engagement Optimization",
"description": "Data-driven insights and suggestions to maximize likes, shares, comments, and overall engagement."
},
{
"title": "🕒 Optimal Timing",
"description": "AI recommendations for the best times to post based on your audience's activity patterns."
},
{
"title": "🔍 Hashtag Intelligence",
"description": "Smart hashtag suggestions and trending topic analysis to increase your content's discoverability."
}
]
for idx, feature in enumerate(features):
with feature_cols[idx % 2]:
st.markdown(f"""
<div class="premium-card" style="min-height: 160px; cursor: default;">
<div class="card-glow"></div>
<div class="card-content">
<div class="card-title" style="margin-bottom: 0.8rem;">{feature['title']}</div>
<div class="card-description" style="margin-bottom: 0;">{feature['description']}</div>
</div>
<div class="card-shine"></div>
</div>
""", unsafe_allow_html=True)