Revert changes to website_setup.py from commit 372bf71

This commit is contained in:
ajaysi
2025-04-17 19:22:29 +05:30
parent 5da0562aa5
commit fa4097c9ae
6 changed files with 277 additions and 280 deletions

View File

@@ -1,20 +1,20 @@
"""API key manager components package."""
from .website_setup import render_website_setup
from .ai_research_setup import render_ai_research_setup
from .ai_providers import render_ai_providers
from .final_setup import render_final_setup
from .personalization_setup import render_personalization_setup
from .alwrity_integrations import render_alwrity_integrations
from .base import render_navigation_buttons, render_step_indicator
from .website_setup import render_website_setup
__all__ = [
'render_website_setup',
'render_ai_research_setup',
'render_ai_providers',
'render_final_setup',
'render_personalization_setup',
'render_alwrity_integrations',
'render_navigation_buttons',
'render_step_indicator'
'render_step_indicator',
'render_website_setup'
]

View File

@@ -41,24 +41,12 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
with col1:
st.markdown("### The Usual")
# SerpAPI Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🔎</div>
<div class="ai-provider-title">SerpAPI</div>
</div>
<div class="ai-provider-description">
Access search engine results for research
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
serpapi_key = st.text_input(
"SerpAPI Key",
"## Enter 🔎 SerpAPI",
type="password",
key="serpapi_key",
help="Enter your SerpAPI key"
help="Enter your SerpAPI key",
placeholder="Access search engine results for research"
)
if serpapi_key:
@@ -88,31 +76,19 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
""", unsafe_allow_html=True)
st.markdown("</div></div>", unsafe_allow_html=True)
# Firecrawl Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🕷️</div>
<div class="ai-provider-title">Firecrawl</div>
</div>
<div class="ai-provider-description">
Web content extraction and analysis
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
firecrawl_key = st.text_input(
"Firecrawl API Key",
"Enter 🕷️ Firecrawl API Key",
type="password",
key="firecrawl_key",
help="Enter your Firecrawl API key"
help="Enter your Firecrawl API key",
placeholder="Web content extraction and analysis"
)
if firecrawl_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
Firecrawl API key configured
</div>
""", unsafe_allow_html=True)
@@ -140,30 +116,18 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
with col2:
st.markdown("### AI Deep Research")
# Tavily Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🤖</div>
<div class="ai-provider-title">Tavily AI</div>
</div>
<div class="ai-provider-description">
AI-powered search with semantic understanding
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
tavily_key = st.text_input(
"Tavily API Key",
"Enter 🤖 Tavily API Key",
type="password",
key="tavily_key",
help="Enter your Tavily API key"
help="Enter your Tavily API key",
placeholder="AI-powered search with semantic understanding"
)
if tavily_key:
st.markdown("""
<div class="ai-provider-status status-valid">
✓ API key configured
Tavily API key configured
</div>
""", unsafe_allow_html=True)
@@ -188,24 +152,12 @@ def render_ai_research_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
st.markdown("</div></div>", unsafe_allow_html=True)
# Metaphor/Exa Card
st.markdown("""
<div class="ai-provider-card">
<div class="ai-provider-header">
<div class="ai-provider-icon">🧠</div>
<div class="ai-provider-title">Metaphor/Exa</div>
</div>
<div class="ai-provider-description">
Neural search engine for deep research
</div>
<div class="ai-provider-input">
""", unsafe_allow_html=True)
metaphor_key = st.text_input(
"Metaphor/Exa API Key",
"Enter 🧠 Metaphor/Exa API Key",
type="password",
key="metaphor_key",
help="Enter your Metaphor/Exa API key"
help="Enter your Metaphor/Exa API key",
placeholder="Neural search engine for deep research"
)
if metaphor_key:

View File

@@ -68,11 +68,10 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
steps = [
("🔑", "AI LLM", 1),
("🤖", "Website Setup", 2),
("👤", "AI Research", 3),
("🎨", "Personalization", 4),
("🔄", "Integrations", 5),
("", "Complete", 6)
("👤", "AI Research", 2),
("🎨", "Personalization", 3),
("🔄", "Integrations", 4),
("", "Complete", 5)
]
html = '<div class="step-indicator">'
@@ -97,7 +96,7 @@ def render_step_indicator(current_step: int, total_steps: int) -> None:
logger.error(f"Error rendering step indicator: {str(e)}")
st.error("Error displaying step indicator")
def render_navigation_buttons(current_step: int, total_steps: int, changes_made: bool = False) -> bool:
def render_navigation_buttons(current_step: int, total_steps: int, changes_made: bool = True) -> bool:
"""Render the navigation buttons with modern glassmorphic styling.
Args:
@@ -113,13 +112,15 @@ def render_navigation_buttons(current_step: int, total_steps: int, changes_made:
with col1:
if current_step > 1:
if st.button("**← Back**", use_container_width=True, key="back_button"):
st.session_state['current_step'] = current_step - 1
from ..wizard_state import previous_step
previous_step()
st.rerun()
with col3:
if current_step < total_steps:
next_text = "**Continue →**"
if st.button(next_text, use_container_width=True, disabled=not changes_made, key="next_button"):
# Don't call next_step() here, let the component handle it
return True
else:
if st.button("**Complete Setup ✓**", use_container_width=True, type="primary", key="complete_button"):

View File

@@ -702,8 +702,8 @@ def render_personalization_setup(api_key_manager: APIKeyManager) -> Dict[str, An
st.success("Configuration saved successfully!")
# Navigation buttons with correct arguments
if render_navigation_buttons(4, 5, changes_made=True):
st.session_state.current_step = 5
if render_navigation_buttons(3, 4, changes_made=True):
st.session_state.current_step = 4
st.rerun()
return {"current_step": 4, "changes_made": True}
return {"current_step": 3, "changes_made": True}

View File

@@ -2,208 +2,265 @@
import streamlit as st
from loguru import logger
# Removed website_analyzer imports as analysis is separate now
# from ...website_analyzer import analyze_website
# from ...website_analyzer.seo_analyzer import analyze_seo
from ...website_analyzer import analyze_website
from ...website_analyzer.seo_analyzer import analyze_seo
import asyncio
import sys
from typing import Dict, Any
import requests
import ssl
import socket
from urllib.parse import urlparse
from ..manager import APIKeyManager
# Navigation is handled in base.py now
# from .base import render_navigation_buttons
# Configure logger (minimal example)
logger.add(sys.stderr, level="INFO")
# --- Validation Helpers ---
def _is_valid_url_format(url: str) -> bool:
"""Checks if the URL has a valid basic format (scheme and netloc)."""
try:
result = urlparse(url)
return all([result.scheme in ['http', 'https'], result.netloc])
except ValueError:
return False
def _check_url_reachability(url: str) -> tuple[bool, str]:
"""Checks if the URL is reachable and returns status code or error."""
try:
response = requests.head(url, allow_redirects=True, timeout=5) # HEAD request is faster
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
logger.info(f"URL {url} reachable, status code: {response.status_code}")
return True, f"Reachable (Status: {response.status_code})"
except requests.exceptions.Timeout:
logger.warning(f"URL {url} timed out.")
return False, "Timeout: Server did not respond in time."
except requests.exceptions.RequestException as e:
logger.warning(f"URL {url} not reachable: {e}")
# Provide a more user-friendly message for common errors
if isinstance(e, requests.exceptions.ConnectionError):
return False, "Connection Error: Could not connect to the server."
elif isinstance(e, requests.exceptions.HTTPError):
return False, f"HTTP Error: {e.response.status_code}"
return False, f"Error: {type(e).__name__}"
def _check_ssl_certificate(url: str) -> tuple[bool, str]:
"""Checks if the URL has a valid SSL certificate (for https)."""
parsed_url = urlparse(url)
if parsed_url.scheme != 'https':
return True, "(HTTP URL)" # Not applicable for http
hostname = parsed_url.netloc
port = 443
context = ssl.create_default_context()
try:
with socket.create_connection((hostname, port), timeout=3) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
# Basic check: does it exist? More thorough checks (expiry, chain) are possible
if cert:
logger.info(f"SSL certificate found for {hostname}")
return True, "Valid SSL Certificate"
else:
logger.warning(f"No SSL certificate found for {hostname}")
return False, "No SSL Certificate found"
except ssl.SSLCertVerificationError as e:
logger.warning(f"SSL Verification Error for {hostname}: {e}")
return False, f"SSL Verification Error: {e.verify_message}"
except socket.timeout:
logger.warning(f"SSL check timed out for {hostname}")
return False, "SSL Check Timeout"
except Exception as e:
logger.error(f"Error checking SSL for {hostname}: {e}", exc_info=True)
return False, f"SSL Check Error: {type(e).__name__}"
# --- Main Component Logic ---
def _validate_website_url(url: str) -> tuple[str, str]:
"""Performs quick validation (format, reachability, basic SSL)."""
if not url:
return "unsaved", ""
# 1. Format Check
if not _is_valid_url_format(url):
logger.warning(f"Invalid URL format: {url}")
return "invalid_format", "Invalid URL format. Please include http:// or https://"
# 2. Reachability Check
reachable, reach_status = _check_url_reachability(url)
if not reachable:
logger.warning(f"URL not reachable: {url} ({reach_status})")
return "unreachable", reach_status # Return specific error message
# 3. Basic SSL Check (only if reachable and HTTPS)
if urlparse(url).scheme == 'https':
ssl_valid, ssl_status = _check_ssl_certificate(url)
if not ssl_valid:
logger.warning(f"SSL check failed for {url} ({ssl_status})")
return "ssl_error", ssl_status # Return specific error message
logger.info(f"URL validation successful for: {url}")
return "valid", "URL is valid and reachable."
def _handle_website_url_change(api_key_manager: APIKeyManager):
"""Save and validate website URL when input changes."""
url_input_widget_key = "website_url_input"
status_widget_key = "website_url_status"
if url_input_widget_key not in st.session_state:
logger.warning(f"Input widget key '{url_input_widget_key}' not found.")
return
url_value = st.session_state[url_input_widget_key]
logger.debug(f"Handling website URL change. URL: {url_value}")
# Save the URL regardless of validity for now (maybe refine later)
# api_key_manager might not be the right place, consider storing directly in session state
# or a dedicated config manager if this isn't an API key.
# Let's store in session_state for now.
st.session_state['configured_website_url'] = url_value
logger.info(f"Saved website URL to session state: {url_value}")
if not url_value:
st.session_state[status_widget_key] = ("unsaved", "")
logger.info("Cleared website URL.")
return
st.session_state[status_widget_key] = ("saving", "") # Indicate validation is running
st.rerun()
try:
validation_status, message = _validate_website_url(url_value)
st.session_state[status_widget_key] = (validation_status, message)
logger.info(f"Website URL validation complete. Status: {validation_status}, Msg: {message}")
except Exception as e:
logger.error(f"Error during website URL validation: {e}", exc_info=True)
st.session_state[status_widget_key] = ("error", "An unexpected error occurred during validation.")
from .base import render_navigation_buttons
# Configure logger to output to both file and stdout
logger.remove() # Remove default handler
logger.add(
"logs/website_setup.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
def render_website_setup(api_key_manager: APIKeyManager) -> Dict[str, Any]:
"""Render the website setup step with immediate feedback."""
"""Render the website setup step.
Args:
api_key_manager (APIKeyManager): The API key manager instance
Returns:
Dict[str, Any]: Current state
"""
logger.info("[render_website_setup] Rendering website setup component")
status_key = "website_url_status"
# Initialize status
if status_key not in st.session_state:
st.session_state[status_key] = ("unsaved", "")
# Optionally pre-validate if a URL exists from previous session/config
# pre_existing_url = api_key_manager.get_config("website_url") # Example
# if pre_existing_url:
# st.session_state[status_key] = _validate_website_url(pre_existing_url)
st.markdown("""
<div class='setup-header'>
<h2>Step 2: Website Setup (Optional)</h2>
<p>Enter your primary website URL. This helps Alwrity personalize suggestions and analyze your content.</p>
</div>
""", unsafe_allow_html=True)
st.markdown("### Step 2: Website Setup")
# Get current value from session state if available, otherwise empty
current_url = st.session_state.get('configured_website_url', "")
# Create two columns for input and results
col1, col2 = st.columns([1, 1])
st.text_input(
"Website URL",
value=current_url,
placeholder="https://example.com",
key="website_url_input",
on_change=_handle_website_url_change,
args=(api_key_manager,) # Pass manager if needed by save logic
)
with col1:
st.markdown("#### Enter Website URL")
url = st.text_input("Website URL", placeholder="https://example.com")
logger.debug(f"[render_website_setup] URL input value: {url}")
analyze_type = st.radio(
"Analysis Type",
["Basic Analysis", "Full Analysis with SEO"],
help="Choose between basic website analysis or comprehensive SEO analysis"
)
if st.button("Analyze Website"):
if url:
with st.spinner("Analyzing website..."):
try:
logger.info(f"[render_website_setup] Starting website analysis for URL: {url}")
# Call the analyze_website function
results = analyze_website(url)
# If full analysis is selected, add SEO analysis
if analyze_type == "Full Analysis with SEO":
seo_results = analyze_seo(url)
if seo_results.success:
results['data']['seo_analysis'] = {
'overall_score': seo_results.overall_score,
'meta_tags': {
'title': seo_results.meta_tags.title,
'description': seo_results.meta_tags.description,
'keywords': seo_results.meta_tags.keywords,
'has_robots': seo_results.meta_tags.has_robots,
'has_sitemap': seo_results.meta_tags.has_sitemap
},
'content': {
'word_count': seo_results.content.word_count,
'readability_score': seo_results.content.readability_score,
'content_quality_score': seo_results.content.content_quality_score,
'headings_structure': seo_results.content.headings_structure,
'keyword_density': seo_results.content.keyword_density
},
'recommendations': [
{
'priority': rec.priority,
'category': rec.category,
'issue': rec.issue,
'recommendation': rec.recommendation,
'impact': rec.impact
}
for rec in seo_results.recommendations
]
}
logger.debug(f"[render_website_setup] Analysis results received: {results.get('success', False)}")
# Store results in session state
st.session_state.website_analysis = results
logger.info("[render_website_setup] Results stored in session state")
if not results.get('success', False):
error_msg = results.get('error', 'Analysis failed')
logger.error(f"[render_website_setup] Analysis failed: {error_msg}")
st.error(error_msg)
else:
logger.info("[render_website_setup] Analysis completed successfully")
st.success("✅ Website analysis completed successfully!")
except Exception as e:
error_msg = f"Analysis failed: {str(e)}"
logger.error(f"[render_website_setup] {error_msg}")
st.error(error_msg)
else:
logger.warning("[render_website_setup] No URL provided")
st.warning("Please enter a valid URL")
# --- Feedback Area ---
status, message = st.session_state.get(status_key, ("unsaved", ""))
feedback_placeholder = st.empty()
with col2:
st.markdown("#### Analysis Results")
# Check if we have analysis results
if 'website_analysis' in st.session_state:
results = st.session_state.website_analysis
if results.get('success', False):
data = results.get('data', {})
analysis = data.get('analysis', {})
# Create tabs for different sections
if analyze_type == "Full Analysis with SEO":
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"Basic Metrics",
"Content Analysis",
"SEO Analysis",
"Technical SEO",
"Strategy"
])
else:
tab1, tab2, tab3, tab4 = st.tabs([
"Basic Metrics",
"Content Analysis",
"Technical Info",
"Strategy"
])
with tab1:
st.markdown("##### Basic Metrics")
basic_info = analysis.get('basic_info', {})
st.write(f"Status Code: {basic_info.get('status_code')}")
st.write(f"Content Type: {basic_info.get('content_type')}")
st.write(f"Title: {basic_info.get('title')}")
st.write(f"Meta Description: {basic_info.get('meta_description')}")
# SSL Info
ssl_info = analysis.get('ssl_info', {})
if ssl_info.get('has_ssl'):
st.success("SSL Certificate is valid")
st.write(f"Expiry: {ssl_info.get('expiry')}")
else:
st.error("No valid SSL certificate found")
with tab2:
st.markdown("##### Content Analysis")
content_info = analysis.get('content_info', {})
# Content Overview
st.markdown("###### 📊 Content Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Word Count", content_info.get('word_count', 0))
with col2:
st.metric("Headings", content_info.get('heading_count', 0))
with col3:
st.metric("Images", content_info.get('image_count', 0))
with col4:
st.metric("Links", content_info.get('link_count', 0))
if analyze_type == "Full Analysis with SEO":
with tab3:
st.markdown("##### SEO Analysis")
seo_data = data.get('seo_analysis', {})
# Display SEO Score
seo_score = seo_data.get('overall_score', 0)
st.markdown(f"### SEO Score: {seo_score}/100")
st.progress(seo_score / 100)
# Meta Tags Analysis
st.markdown("#### Meta Tags Analysis")
meta_analysis = seo_data.get('meta_tags', {})
for key, value in meta_analysis.items():
if isinstance(value, bool):
st.write(f"{'' if value else ''} {key.replace('_', ' ').title()}")
elif isinstance(value, dict):
st.write(f"**{key.replace('_', ' ').title()}:**")
st.write(f"Status: {value.get('status', 'N/A')}")
st.write(f"Value: {value.get('value', 'N/A')}")
if value.get('recommendation'):
st.write(f"Recommendation: {value['recommendation']}")
else:
st.write(f"**{key.replace('_', ' ').title()}:** {value}")
# Content Analysis
st.markdown("#### AI Content Analysis")
content_analysis = seo_data.get('content', {})
st.write(f"**Word Count:** {content_analysis.get('word_count', 0)}")
st.write(f"**Readability Score:** {content_analysis.get('readability_score', 0)}/100")
st.write(f"**Content Quality Score:** {content_analysis.get('content_quality_score', 0)}/100")
# Recommendations
st.markdown("#### SEO Recommendations")
recommendations = seo_data.get('recommendations', [])
for rec in recommendations:
st.write(f"**{rec.get('priority', '').upper()} Priority - {rec.get('category', '')}**")
st.write(f"Issue: {rec.get('issue', '')}")
st.write(f"Recommendation: {rec.get('recommendation', '')}")
st.write(f"Impact: {rec.get('impact', '')}")
st.write("---")
with tab4:
st.markdown("##### Technical SEO")
technical_seo = seo_data.get('technical_analysis', {})
# Mobile Friendliness
st.markdown("#### Mobile Friendliness")
mobile_friendly = technical_seo.get('mobile_friendly', False)
st.write(f"{'' if mobile_friendly else ''} Mobile Friendly")
# Page Speed
st.markdown("#### Page Speed")
speed_metrics = technical_seo.get('speed_metrics', {})
for metric, value in speed_metrics.items():
st.write(f"**{metric.replace('_', ' ').title()}:** {value}")
# Technical Issues
st.markdown("#### Technical Issues")
issues = technical_seo.get('issues', [])
for issue in issues:
st.write(f"{issue}")
with tab4 if analyze_type == "Basic Analysis" else tab5:
st.markdown("##### Strategy Recommendations")
strategy_info = analysis.get('strategy', {})
if strategy_info:
for category, recommendations in strategy_info.items():
st.markdown(f"###### {category.replace('_', ' ').title()}")
for rec in recommendations:
st.write(f"{rec}")
else:
st.info("No strategy recommendations available")
else:
error_msg = results.get('error', 'Analysis failed')
logger.error(f"[render_website_setup] Displaying error: {error_msg}")
st.error(error_msg)
else:
logger.debug("[render_website_setup] No analysis results in session state")
st.info("Enter a URL and click 'Analyze Website' to see results")
if status == "saving":
feedback_placeholder.info("Validating URL...", icon="")
elif status == "valid":
feedback_placeholder.success(message, icon="")
elif status == "invalid_format":
feedback_placeholder.error(f"Format Error: {message}", icon="")
elif status == "unreachable":
feedback_placeholder.error(f"Reachability Error: {message}", icon="")
elif status == "ssl_error":
feedback_placeholder.warning(f"SSL Warning: {message}", icon="⚠️") # Warning for SSL
elif status == "error":
feedback_placeholder.error(f"Validation Error: {message}", icon="⚠️")
elif status == "unsaved" and current_url: # Show warning if field has text but isn't validated yet
feedback_placeholder.warning("URL not yet validated.", icon="⚠️")
# --- Removed Analysis Section ---
# The detailed website analysis should be a separate feature, not part of the initial setup validation.
st.markdown("---")
st.markdown(" *The detailed Website Analyzer tool is available separately in the main application.*")
st.info("Entering your website URL is optional. Click Continue to proceed.")
# Return value is not strictly needed if navigation relies on session state status
return {}
# Removed old analysis logic and button handling as it's handled in base.py
# Navigation buttons
if render_navigation_buttons(2, 5, True):
# Move to next step (AI Research Setup)
st.session_state.current_step = 3
st.session_state.next_step = "ai_research_setup"
st.rerun()
return {"current_step": 2, "changes_made": True}

View File

@@ -1,13 +0,0 @@
import json
import os
import streamlit as st
def save_config(config):
"""
Saves the provided configuration dictionary to a JSON file specified by the environment variable.
"""
try:
with open(os.getenv("ALWRITY_CONFIG"), "w") as config_file:
json.dump(config, config_file, indent=4)
except Exception as e:
st.error(f"An error occurred while saving the configuration: {e}")