Files
ALwrity/lib/ai_seo_tools/content_gap_analysis/ui.py

769 lines
31 KiB
Python

"""
Streamlit UI for Content Gap Analysis workflow.
"""
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import json
from datetime import datetime
from .main import ContentGapAnalysis
from .keyword_researcher import KeywordResearcher
from .competitor_analyzer import CompetitorAnalyzer
from .website_analyzer import WebsiteAnalyzer
from .recommendation_engine import RecommendationEngine
from .utils.ai_processor import AIProcessor
from .navigation import show_content_gap_analysis_nav
from typing import Dict, Any
import logging
# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class ContentGapAnalysisUI:
"""Streamlit UI for Content Gap Analysis workflow."""
def __init__(self):
"""Initialize the UI components."""
# Initialize session state for progress tracking
if 'current_step' not in st.session_state:
st.session_state.current_step = 1
if 'analysis_results' not in st.session_state:
st.session_state.analysis_results = {}
# Initialize analysis components
self.analyzer = ContentGapAnalysis()
self.keyword_researcher = KeywordResearcher()
self.competitor_analyzer = CompetitorAnalyzer()
self.website_analyzer = WebsiteAnalyzer()
self.recommendation_engine = RecommendationEngine()
self.ai_processor = AIProcessor()
def run(self):
"""Run the Streamlit interface."""
try:
# Show navigation
nav_option = show_content_gap_analysis_nav()
# Main content area
st.title("Content Gap Analysis")
st.markdown("""
This tool helps you identify content gaps and opportunities by analyzing your website,
competitors, and market trends. Follow the steps below to get started.
""")
# Progress tracking
self._show_progress()
# Main workflow steps
if nav_option == "Website Analysis" or st.session_state.current_step == 1:
self._website_analysis_step()
elif nav_option == "Competitor Analysis" or st.session_state.current_step == 2:
self._competitor_analysis_step()
elif nav_option == "Keyword Research" or st.session_state.current_step == 3:
self._keyword_research_step()
elif nav_option == "Recommendations" or st.session_state.current_step == 4:
self._recommendations_step()
else:
self._export_results()
except Exception as e:
logger.error(f"Error in run method: {str(e)}", exc_info=True)
st.error(f"An error occurred: {str(e)}")
def _show_progress(self):
"""Display progress tracking."""
steps = [
"Website Analysis",
"Competitor Analysis",
"Keyword Research",
"Recommendations",
"Export Results"
]
progress = st.session_state.current_step / len(steps)
st.progress(progress)
cols = st.columns(len(steps))
for i, col in enumerate(cols):
with col:
if i + 1 < st.session_state.current_step:
st.success(f"{steps[i]}")
elif i + 1 == st.session_state.current_step:
st.info(f"{steps[i]}")
else:
st.text(f"{steps[i]}")
def _website_analysis_step(self):
"""Website analysis step UI."""
try:
st.header("Step 1: Website Analysis")
# Display previous results if they exist
if 'website' in st.session_state.analysis_results:
st.info("Previous analysis results found. You can analyze a new website or proceed to the next step.")
self._display_website_analysis(st.session_state.analysis_results['website'])
col1, col2 = st.columns(2)
with col1:
if st.button("Analyze New Website"):
st.session_state.analysis_results.pop('website', None)
st.rerun()
with col2:
if st.button("Proceed to Competitor Analysis"):
st.session_state.current_step = 2
st.rerun()
return
# Create form for new analysis
with st.form("website_analysis_form"):
website_url = st.text_input("Enter your website URL")
industry = st.text_input("Enter your industry/niche")
submitted = st.form_submit_button("Analyze Website")
# Handle form submission outside the form
if submitted and website_url and industry:
# Initialize progress tracking
if 'analysis_progress' not in st.session_state:
st.session_state.analysis_progress = {
'status': 'initializing',
'current_step': 'Starting Analysis',
'progress': 0,
'details': 'Initializing analysis...'
}
# Create progress container
progress_container = st.empty()
status_container = st.empty()
details_container = st.empty()
# Update progress display
def update_progress_display():
progress = st.session_state.analysis_progress
# Update progress bar
with progress_container:
st.progress(progress['progress'] / 100)
# Update status
with status_container:
if progress['status'] == 'error':
st.error(f"Error: {progress['current_step']}")
elif progress['status'] == 'completed':
st.success(f"{progress['current_step']}")
else:
st.info(f"{progress['current_step']}")
# Update details
with details_container:
st.write(progress['details'])
# Initial progress display
update_progress_display()
try:
# Get basic analysis
results = self.website_analyzer.analyze(website_url)
# Update progress from analyzer
st.session_state.analysis_progress = self.website_analyzer.progress.get_progress()
update_progress_display()
if isinstance(results, dict) and 'error' in results:
st.error(f"Error in website analysis: {results['error']}")
return
# Get AI-enhanced analysis
st.session_state.analysis_progress.update({
'current_step': 'AI Analysis',
'progress': 95,
'details': 'Performing AI-enhanced analysis...'
})
update_progress_display()
ai_analysis = self.ai_processor.analyze_content({
'url': website_url,
'industry': industry,
'content': results
})
# Combine results
if isinstance(results, dict):
results.update(ai_analysis)
else:
results = {'error': 'Invalid analysis results format'}
# Store results in session state
st.session_state.analysis_results['website'] = results
# Update final progress
st.session_state.analysis_progress.update({
'status': 'completed',
'current_step': 'Analysis Complete',
'progress': 100,
'details': 'Analysis completed successfully!'
})
update_progress_display()
# Display results
self._display_website_analysis(results)
except Exception as e:
logger.error(f"Error during website analysis: {str(e)}", exc_info=True)
st.session_state.analysis_progress.update({
'status': 'error',
'current_step': 'Analysis Failed',
'details': f"Error during website analysis: {str(e)}"
})
update_progress_display()
st.error(f"Error during website analysis: {str(e)}")
return
except Exception as e:
logger.error(f"Error in website analysis step: {str(e)}", exc_info=True)
st.error(f"Error in website analysis: {str(e)}")
def _display_website_analysis(self, results: Dict[str, Any]):
"""Display website analysis results."""
try:
if not isinstance(results, dict):
st.error("Invalid analysis results format")
return
if 'error' in results:
st.error(f"Error in analysis: {results['error']}")
return
# Content Metrics
st.subheader("Content Metrics")
content_metrics = results.get('content_metrics', {})
if content_metrics:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Word Count", f"{content_metrics.get('word_count', 0):,}")
with col2:
st.metric("Headings", f"{content_metrics.get('heading_count', 0):,}")
with col3:
st.metric("Images", f"{content_metrics.get('image_count', 0):,}")
with col4:
st.metric("Links", f"{content_metrics.get('link_count', 0):,}")
# Content Structure Visualization
st.write("Content Structure")
heading_data = {
'Type': ['H1', 'H2', 'H3', 'Paragraphs'],
'Count': [
content_metrics.get('h1_count', 0),
content_metrics.get('h2_count', 0),
content_metrics.get('h3_count', 0),
content_metrics.get('paragraph_count', 0)
]
}
fig = px.bar(
heading_data,
x='Type',
y='Count',
title="Content Structure Distribution",
color='Type',
color_discrete_sequence=px.colors.qualitative.Set3
)
st.plotly_chart(fig, use_container_width=True)
# Content Features
st.write("Content Features")
features = {
'Feature': ['Meta Description', 'Robots.txt', 'Sitemap'],
'Status': [
content_metrics.get('has_meta_description', False),
content_metrics.get('has_robots_txt', False),
content_metrics.get('has_sitemap', False)
]
}
fig = px.bar(
features,
x='Feature',
y='Status',
title="Content Features Status",
color='Status',
color_discrete_sequence=['red', 'green']
)
st.plotly_chart(fig, use_container_width=True)
# SEO Metrics
st.subheader("SEO Metrics")
seo_metrics = results.get('seo_metrics', {})
if seo_metrics:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Overall Score", f"{seo_metrics.get('overall_score', 0):.1f}%")
with col2:
content_quality = seo_metrics.get('content', {}).get('content_quality_score', 0)
st.metric("Content Quality", f"{content_quality:.1f}%")
with col3:
readability = seo_metrics.get('content', {}).get('readability_score', 0)
st.metric("Readability", f"{readability:.1f}%")
with col4:
keyword_density = seo_metrics.get('content', {}).get('keyword_density', 0)
st.metric("Keyword Density", f"{keyword_density:.1f}%")
# SEO Scores Radar Chart
seo_scores = {
'Metric': ['Overall', 'Content Quality', 'Readability', 'Keyword Density'],
'Score': [
seo_metrics.get('overall_score', 0),
content_quality,
readability,
keyword_density
]
}
fig = px.line_polar(
seo_scores,
r='Score',
theta='Metric',
line_close=True,
title="SEO Performance Overview"
)
fig.update_traces(fill='toself')
st.plotly_chart(fig, use_container_width=True)
# Meta Tags Analysis
st.write("Meta Tags Analysis")
meta_tags = seo_metrics.get('meta_tags', {})
if meta_tags:
# Title Analysis
title = meta_tags.get('title', {})
st.write("Title Tag")
st.write(f"Status: {'' if title.get('status') == 'good' else ''}")
st.write(f"Value: {title.get('value', 'N/A')}")
st.write(f"Length: {title.get('length', 0)} characters")
st.write(f"Score: {title.get('score', 0)}%")
if title.get('recommendation'):
st.warning(title.get('recommendation'))
# Description Analysis
desc = meta_tags.get('description', {})
st.write("Meta Description")
st.write(f"Status: {'' if desc.get('status') == 'good' else ''}")
st.write(f"Value: {desc.get('value', 'N/A')}")
st.write(f"Length: {desc.get('length', 0)} characters")
st.write(f"Score: {desc.get('score', 0)}%")
if desc.get('recommendation'):
st.warning(desc.get('recommendation'))
# Keywords Analysis
keywords = meta_tags.get('keywords', {})
st.write("Meta Keywords")
st.write(f"Status: {'' if keywords.get('status') == 'good' else ''}")
st.write(f"Value: {keywords.get('value', 'N/A')}")
if keywords.get('recommendation'):
st.warning(keywords.get('recommendation'))
# Technical Metrics
st.subheader("Technical Metrics")
technical_info = results.get('technical_info', {})
if technical_info:
col1, col2 = st.columns(2)
with col1:
st.write("Basic Information")
st.metric("Status Code", technical_info.get('status_code', 'N/A'))
st.metric("Server", technical_info.get('server_info', {}).get('server', 'N/A'))
st.metric("Content Type", technical_info.get('server_info', {}).get('content_type', 'N/A'))
with col2:
st.write("Security Information")
security_info = technical_info.get('security_info', {})
security_data = {
'Feature': ['SSL', 'HSTS', 'XSS Protection'],
'Status': [
security_info.get('ssl', False),
security_info.get('hsts', False),
security_info.get('xss_protection', False)
]
}
fig = px.bar(
security_data,
x='Feature',
y='Status',
title="Security Features Status",
color='Status',
color_discrete_sequence=['red', 'green']
)
st.plotly_chart(fig, use_container_width=True)
# Performance Metrics
st.subheader("Performance Metrics")
performance = results.get('performance', {})
if performance:
# Basic metrics in columns
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Load Time", f"{performance.get('load_time', 0):.2f}s")
with col2:
st.metric("Page Size", f"{performance.get('page_size', 0):.1f} KB")
with col3:
st.metric("Status Code", performance.get('status_code', 'N/A'))
with col4:
st.metric("Response Time", f"{performance.get('response_time', 0):.2f}s")
# Insights and Recommendations
st.subheader("Insights and Recommendations")
insights = results.get('insights', [])
if insights:
for insight in insights:
st.info(f"{insight}")
else:
st.info("No specific insights available")
except Exception as e:
logger.error(f"Error displaying website analysis: {str(e)}", exc_info=True)
st.error(f"Error displaying website analysis: {str(e)}")
def _competitor_analysis_step(self):
"""Competitor analysis step UI."""
try:
st.header("Step 2: Competitor Analysis")
with st.form("competitor_analysis_form"):
competitors = st.text_area(
"Enter competitor URLs (one per line)",
help="Enter the URLs of your main competitors"
)
submitted = st.form_submit_button("Analyze Competitors")
if submitted and competitors:
with st.spinner("Analyzing competitors..."):
competitor_urls = [url.strip() for url in competitors.split('\n') if url.strip()]
results = self.competitor_analyzer.analyze(competitor_urls)
# Get AI-enhanced competitor analysis
ai_analysis = self.ai_processor.analyze_competitors({
'competitors': competitor_urls,
'analysis': results
})
# Combine results
results.update(ai_analysis)
st.session_state.analysis_results['competitors'] = results
# Display results
self._display_competitor_analysis(results)
# Move to next step
st.session_state.current_step = 3
st.rerun()
except Exception as e:
logger.error(f"Error in competitor analysis step: {str(e)}", exc_info=True)
st.error(f"Error in competitor analysis: {str(e)}")
def _display_competitor_analysis(self, results: dict):
"""Display competitor analysis results."""
st.subheader("Competitor Analysis Results")
# Competitor comparison
st.subheader("Competitor Comparison")
comp_data = pd.DataFrame(results.get('comparison', []))
if not comp_data.empty:
fig = px.bar(
comp_data,
x='competitor',
y='score',
color='metric',
title="Competitor Comparison"
)
st.plotly_chart(fig)
# AI-Enhanced Competitor Analysis
st.subheader("AI-Enhanced Competitor Analysis")
# Competitor Trend Analysis
trend_data = results.get('competitor_trends', {})
if trend_data:
fig = go.Figure()
for competitor, trends in trend_data.items():
fig.add_trace(go.Scatter(
x=trends.get('timeline', []),
y=trends.get('scores', []),
name=competitor,
mode='lines+markers'
))
fig.update_layout(
title="Competitor Performance Trends",
xaxis_title="Timeline",
yaxis_title="Score"
)
st.plotly_chart(fig)
# Content gaps
st.subheader("Content Gaps")
gaps = results.get('content_gaps', [])
for gap in gaps:
st.info(f"{gap}")
# AI-Generated Competitive Insights
st.subheader("Competitive Insights")
insights = results.get('competitive_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Analysis"):
for point in points:
st.success(f"{point}")
def _keyword_research_step(self):
"""Keyword research step UI."""
try:
st.header("Step 3: Keyword Research")
with st.form("keyword_research_form"):
industry = st.text_input(
"Enter your industry/niche",
value=st.session_state.analysis_results.get('website', {}).get('industry', '')
)
submitted = st.form_submit_button("Research Keywords")
if submitted and industry:
with st.spinner("Researching keywords..."):
results = self.keyword_researcher.research(industry)
# Get AI-enhanced keyword analysis
ai_analysis = self.ai_processor.analyze_keywords({
'industry': industry,
'keywords': results
})
# Combine results
results.update(ai_analysis)
st.session_state.analysis_results['keywords'] = results
# Display results
self._display_keyword_research(results)
# Move to next step
st.session_state.current_step = 4
st.rerun()
except Exception as e:
logger.error(f"Error in keyword research step: {str(e)}", exc_info=True)
st.error(f"Error in keyword research: {str(e)}")
def _display_keyword_research(self, results: dict):
"""Display keyword research results."""
st.subheader("Keyword Research Results")
# Keyword metrics
st.subheader("Keyword Metrics")
keyword_data = pd.DataFrame(results.get('keywords', []))
if not keyword_data.empty:
fig = px.scatter(
keyword_data,
x='search_volume',
y='difficulty',
size='relevance_score',
hover_data=['keyword'],
title="Keyword Opportunities"
)
st.plotly_chart(fig)
# AI-Enhanced Keyword Analysis
st.subheader("AI-Enhanced Keyword Analysis")
# Keyword Trend Analysis
trend_data = results.get('keyword_trends', {})
if trend_data:
fig = go.Figure()
for keyword, trends in trend_data.items():
fig.add_trace(go.Scatter(
x=trends.get('timeline', []),
y=trends.get('scores', []),
name=keyword,
mode='lines+markers'
))
fig.update_layout(
title="Keyword Trend Analysis",
xaxis_title="Timeline",
yaxis_title="Trend Score"
)
st.plotly_chart(fig)
# Search intent distribution
st.subheader("Search Intent Distribution")
intent_data = pd.DataFrame(results.get('search_intent', {}).get('summary', {}))
if not intent_data.empty:
fig = px.pie(
intent_data,
values='count',
names='intent',
title="Search Intent Distribution"
)
st.plotly_chart(fig)
# Content format suggestions
st.subheader("Content Format Suggestions")
formats = results.get('content_formats', [])
for format in formats:
st.info(f"{format}")
# AI-Generated Keyword Insights
st.subheader("Keyword Insights")
insights = results.get('keyword_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Insights"):
for point in points:
st.success(f"{point}")
def _recommendations_step(self):
"""Recommendations step UI."""
try:
st.header("Step 4: Content Recommendations")
with st.spinner("Generating recommendations..."):
results = self.recommendation_engine.generate_recommendations(
st.session_state.analysis_results
)
# Get AI-enhanced recommendations
ai_recommendations = self.ai_processor.analyze_recommendations({
'recommendations': results,
'analysis': st.session_state.analysis_results
})
# Combine results
results.update(ai_recommendations)
st.session_state.analysis_results['recommendations'] = results
# Display results
self._display_recommendations(results)
# Move to next step
st.session_state.current_step = 5
st.rerun()
except Exception as e:
logger.error(f"Error in recommendations step: {str(e)}", exc_info=True)
st.error(f"Error in recommendations: {str(e)}")
def _display_recommendations(self, results: dict):
"""Display content recommendations."""
st.subheader("Content Recommendations")
# Priority recommendations
st.subheader("Priority Recommendations")
priorities = results.get('priorities', [])
for priority in priorities:
st.success(f"{priority}")
# AI-Enhanced Recommendations
st.subheader("AI-Enhanced Recommendations")
# Recommendation Impact Analysis
impact_data = results.get('impact_analysis', {})
if impact_data:
fig = go.Figure()
for metric, values in impact_data.items():
fig.add_trace(go.Bar(
name=metric,
x=values.get('categories', []),
y=values.get('scores', [])
))
fig.update_layout(
title="Recommendation Impact Analysis",
xaxis_title="Categories",
yaxis_title="Impact Score",
barmode='group'
)
st.plotly_chart(fig)
# Implementation timeline
st.subheader("Implementation Timeline")
timeline = results.get('timeline', [])
for item in timeline:
st.info(f"{item}")
# Expected impact
st.subheader("Expected Impact")
impact = results.get('impact', {})
for metric, value in impact.items():
st.metric(metric, value)
# AI-Generated Strategic Insights
st.subheader("Strategic Insights")
insights = results.get('strategic_insights', {})
if insights:
for category, points in insights.items():
with st.expander(f"{category.title()} Strategy"):
for point in points:
st.success(f"{point}")
def _export_results(self):
"""Export results step UI."""
st.header("Step 5: Export Results")
# Export options
export_format = st.radio(
"Choose export format",
["JSON", "CSV", "PDF"]
)
if st.button("Export Results"):
if export_format == "JSON":
self._export_json()
elif export_format == "CSV":
self._export_csv()
else:
st.info("PDF export coming soon!")
def _export_json(self):
"""Export results as JSON."""
results = st.session_state.analysis_results
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"content_gap_analysis_{timestamp}.json"
st.download_button(
"Download JSON",
data=json.dumps(results, indent=2),
file_name=filename,
mime="application/json"
)
def _export_csv(self):
"""Export results as CSV."""
results = st.session_state.analysis_results
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Convert results to CSV format
csv_data = []
for section, data in results.items():
if isinstance(data, list):
for item in data:
if isinstance(item, dict):
item['section'] = section
csv_data.append(item)
elif isinstance(data, dict):
data['section'] = section
csv_data.append(data)
if csv_data:
df = pd.DataFrame(csv_data)
filename = f"content_gap_analysis_{timestamp}.csv"
st.download_button(
"Download CSV",
data=df.to_csv(index=False),
file_name=filename,
mime="text/csv"
)
def main():
"""Main entry point for the Streamlit app."""
ui = ContentGapAnalysisUI()
ui.run()
if __name__ == "__main__":
main()