Spaces:
Sleeping
Sleeping
Update seo_analyzer_ui.py
Browse files- seo_analyzer_ui.py +9 -305
seo_analyzer_ui.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
-
SEO Analyzer UI
|
| 3 |
"""
|
|
|
|
| 4 |
import gradio as gr
|
| 5 |
import logging
|
| 6 |
import json
|
|
@@ -18,9 +19,6 @@ from concurrent.futures import ThreadPoolExecutor
|
|
| 18 |
from datetime import datetime
|
| 19 |
import tempfile
|
| 20 |
|
| 21 |
-
from bs4 import BeautifulSoup
|
| 22 |
-
import requests
|
| 23 |
-
|
| 24 |
from crawler import Crawler
|
| 25 |
from frontier import URLFrontier
|
| 26 |
from models import URL, Page
|
|
@@ -30,6 +28,7 @@ from dotenv import load_dotenv, find_dotenv
|
|
| 30 |
|
| 31 |
load_dotenv(find_dotenv())
|
| 32 |
|
|
|
|
| 33 |
IS_DEPLOYMENT = os.getenv('DEPLOYMENT', 'false').lower() == 'true'
|
| 34 |
|
| 35 |
# Custom CSS for better styling
|
|
@@ -39,26 +38,22 @@ CUSTOM_CSS = """
|
|
| 39 |
margin: auto;
|
| 40 |
padding: 20px;
|
| 41 |
}
|
| 42 |
-
|
| 43 |
.header {
|
| 44 |
text-align: center;
|
| 45 |
margin-bottom: 2rem;
|
| 46 |
}
|
| 47 |
-
|
| 48 |
.header h1 {
|
| 49 |
color: #2d3748;
|
| 50 |
font-size: 2.5rem;
|
| 51 |
font-weight: 700;
|
| 52 |
margin-bottom: 1rem;
|
| 53 |
}
|
| 54 |
-
|
| 55 |
.header p {
|
| 56 |
color: #4a5568;
|
| 57 |
font-size: 1.1rem;
|
| 58 |
max-width: 800px;
|
| 59 |
margin: 0 auto;
|
| 60 |
}
|
| 61 |
-
|
| 62 |
.input-section {
|
| 63 |
background: #f7fafc;
|
| 64 |
border-radius: 12px;
|
|
@@ -66,7 +61,6 @@ CUSTOM_CSS = """
|
|
| 66 |
margin-bottom: 24px;
|
| 67 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 68 |
}
|
| 69 |
-
|
| 70 |
.analysis-section {
|
| 71 |
background: white;
|
| 72 |
border-radius: 12px;
|
|
@@ -74,7 +68,6 @@ CUSTOM_CSS = """
|
|
| 74 |
margin-top: 24px;
|
| 75 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 76 |
}
|
| 77 |
-
|
| 78 |
.log-section {
|
| 79 |
font-family: monospace;
|
| 80 |
background: #1a202c;
|
|
@@ -83,7 +76,6 @@ CUSTOM_CSS = """
|
|
| 83 |
border-radius: 8px;
|
| 84 |
margin-top: 24px;
|
| 85 |
}
|
| 86 |
-
|
| 87 |
/* Custom styling for inputs */
|
| 88 |
.input-container {
|
| 89 |
background: white;
|
|
@@ -91,14 +83,12 @@ CUSTOM_CSS = """
|
|
| 91 |
border-radius: 8px;
|
| 92 |
margin-bottom: 16px;
|
| 93 |
}
|
| 94 |
-
|
| 95 |
/* Custom styling for the slider */
|
| 96 |
.slider-container {
|
| 97 |
padding: 12px;
|
| 98 |
background: white;
|
| 99 |
border-radius: 8px;
|
| 100 |
}
|
| 101 |
-
|
| 102 |
/* Custom styling for buttons */
|
| 103 |
.primary-button {
|
| 104 |
background: #4299e1 !important;
|
|
@@ -108,46 +98,38 @@ CUSTOM_CSS = """
|
|
| 108 |
font-weight: 600 !important;
|
| 109 |
transition: all 0.3s ease !important;
|
| 110 |
}
|
| 111 |
-
|
| 112 |
.primary-button:hover {
|
| 113 |
background: #3182ce !important;
|
| 114 |
transform: translateY(-1px) !important;
|
| 115 |
}
|
| 116 |
-
|
| 117 |
/* Markdown output styling */
|
| 118 |
.markdown-output {
|
| 119 |
font-family: system-ui, -apple-system, sans-serif;
|
| 120 |
line-height: 1.6;
|
| 121 |
}
|
| 122 |
-
|
| 123 |
.markdown-output h1 {
|
| 124 |
color: #2d3748;
|
| 125 |
border-bottom: 2px solid #e2e8f0;
|
| 126 |
padding-bottom: 0.5rem;
|
| 127 |
}
|
| 128 |
-
|
| 129 |
.markdown-output h2 {
|
| 130 |
color: #4a5568;
|
| 131 |
margin-top: 2rem;
|
| 132 |
}
|
| 133 |
-
|
| 134 |
.markdown-output h3 {
|
| 135 |
color: #718096;
|
| 136 |
margin-top: 1.5rem;
|
| 137 |
}
|
| 138 |
-
|
| 139 |
/* Progress bar styling */
|
| 140 |
.progress-bar {
|
| 141 |
height: 8px !important;
|
| 142 |
border-radius: 4px !important;
|
| 143 |
background: #ebf8ff !important;
|
| 144 |
}
|
| 145 |
-
|
| 146 |
.progress-bar-fill {
|
| 147 |
background: #4299e1 !important;
|
| 148 |
border-radius: 4px !important;
|
| 149 |
}
|
| 150 |
-
|
| 151 |
/* Add some spacing between sections */
|
| 152 |
.gap {
|
| 153 |
margin: 2rem 0;
|
|
@@ -434,10 +416,8 @@ class SEOAnalyzer:
|
|
| 434 |
# Format the results
|
| 435 |
formatted_analysis = f"""
|
| 436 |
# SEO Analysis Report for {domain}
|
| 437 |
-
|
| 438 |
## Overall Analysis
|
| 439 |
{overall_analysis}
|
| 440 |
-
|
| 441 |
## Page-Specific Analyses
|
| 442 |
"""
|
| 443 |
for page_analysis in page_analyses:
|
|
@@ -494,16 +474,13 @@ class SEOAnalyzer:
|
|
| 494 |
# Create analysis prompt
|
| 495 |
prompt = f"""
|
| 496 |
You are an expert SEO consultant. Analyze this website's SEO based on the crawled data:
|
| 497 |
-
|
| 498 |
{json.dumps(site_overview, indent=2)}
|
| 499 |
-
|
| 500 |
Provide a comprehensive SEO analysis including:
|
| 501 |
1. Overall site structure and navigation
|
| 502 |
2. Common SEO issues across pages
|
| 503 |
3. Content quality and optimization
|
| 504 |
4. Technical SEO recommendations
|
| 505 |
5. Priority improvements
|
| 506 |
-
|
| 507 |
Format your response in Markdown.
|
| 508 |
"""
|
| 509 |
|
|
@@ -533,17 +510,14 @@ Format your response in Markdown.
|
|
| 533 |
# Create page analysis prompt
|
| 534 |
prompt = f"""
|
| 535 |
Analyze this page's SEO:
|
| 536 |
-
|
| 537 |
URL: {page['url']}
|
| 538 |
Metadata: {json.dumps(page['metadata'], indent=2)}
|
| 539 |
-
|
| 540 |
Provide specific recommendations for:
|
| 541 |
1. Title and meta description
|
| 542 |
2. Heading structure
|
| 543 |
3. Content optimization
|
| 544 |
4. Internal linking
|
| 545 |
5. Technical improvements
|
| 546 |
-
|
| 547 |
Format your response in Markdown.
|
| 548 |
"""
|
| 549 |
|
|
@@ -609,7 +583,6 @@ def create_ui() -> gr.Interface:
|
|
| 609 |
# Create markdown content for the about section
|
| 610 |
about_markdown = """
|
| 611 |
# 🔍 SEO Analyzer Pro
|
| 612 |
-
|
| 613 |
Analyze your website's SEO performance using advanced crawling and AI technology.
|
| 614 |
|
| 615 |
### Features:
|
|
@@ -695,284 +668,15 @@ def create_ui() -> gr.Interface:
|
|
| 695 |
|
| 696 |
return iface
|
| 697 |
|
| 698 |
-
# ----- SEO Analyzer UI (as a function) -----
|
| 699 |
-
def seo_analyzer_ui():
|
| 700 |
-
def analyze(url: str, api_key: str, max_pages: int, progress: gr.Progress = gr.Progress()) -> Tuple[str, str]:
|
| 701 |
-
try:
|
| 702 |
-
analyzer = SEOAnalyzer(api_key)
|
| 703 |
-
analysis, _, logs = analyzer.analyze_website(url, max_pages, progress)
|
| 704 |
-
log_output = ""
|
| 705 |
-
while not analyzer.log_queue.empty():
|
| 706 |
-
try:
|
| 707 |
-
log_output += analyzer.log_queue.get_nowait() + "\n"
|
| 708 |
-
except queue.Empty:
|
| 709 |
-
break
|
| 710 |
-
progress(1.0, "Analysis complete")
|
| 711 |
-
return analysis, log_output
|
| 712 |
-
except Exception as e:
|
| 713 |
-
error_msg = f"Error: {str(e)}"
|
| 714 |
-
logger.error(error_msg)
|
| 715 |
-
return error_msg, error_msg
|
| 716 |
-
|
| 717 |
-
about_markdown = """
|
| 718 |
-
# 🔍 SEO Analyzer Pro
|
| 719 |
-
Analyze your website's SEO performance using advanced crawling and AI technology.
|
| 720 |
-
...
|
| 721 |
-
"""
|
| 722 |
-
with gr.Blocks() as seo_tab:
|
| 723 |
-
gr.Markdown(about_markdown)
|
| 724 |
-
with gr.Row():
|
| 725 |
-
with gr.Column(scale=2):
|
| 726 |
-
with gr.Group(elem_classes="input-section"):
|
| 727 |
-
gr.Markdown("### 📝 Enter Website Details")
|
| 728 |
-
url_input = gr.Textbox(
|
| 729 |
-
label="Website URL",
|
| 730 |
-
placeholder="https://example.com",
|
| 731 |
-
elem_classes="input-container",
|
| 732 |
-
info="Enter the full URL of the website you want to analyze (e.g., https://example.com)"
|
| 733 |
-
)
|
| 734 |
-
api_key = gr.Textbox(
|
| 735 |
-
label="OpenAI API Key",
|
| 736 |
-
placeholder="sk-...",
|
| 737 |
-
type="password",
|
| 738 |
-
elem_classes="input-container",
|
| 739 |
-
info="Your OpenAI API key is required for AI-powered analysis. Keep this secure!"
|
| 740 |
-
)
|
| 741 |
-
max_pages = gr.Slider(
|
| 742 |
-
minimum=1,
|
| 743 |
-
maximum=50,
|
| 744 |
-
value=10,
|
| 745 |
-
step=1,
|
| 746 |
-
label="Maximum Pages to Crawl",
|
| 747 |
-
elem_classes="slider-container",
|
| 748 |
-
info="Choose how many pages to analyze. More pages = more comprehensive analysis but takes longer"
|
| 749 |
-
)
|
| 750 |
-
analyze_btn = gr.Button(
|
| 751 |
-
"🔍 Analyze Website",
|
| 752 |
-
elem_classes="primary-button"
|
| 753 |
-
)
|
| 754 |
-
|
| 755 |
-
with gr.Row():
|
| 756 |
-
with gr.Column():
|
| 757 |
-
with gr.Group(elem_classes="analysis-section"):
|
| 758 |
-
gr.Markdown("### 📊 Analysis Results")
|
| 759 |
-
analysis_output = gr.Markdown(
|
| 760 |
-
label="SEO Analysis",
|
| 761 |
-
elem_classes="markdown-output"
|
| 762 |
-
)
|
| 763 |
-
with gr.Row():
|
| 764 |
-
with gr.Column():
|
| 765 |
-
with gr.Group(elem_classes="log-section"):
|
| 766 |
-
gr.Markdown("### 📋 Process Logs")
|
| 767 |
-
logs_output = gr.Textbox(
|
| 768 |
-
label="Logs",
|
| 769 |
-
lines=10,
|
| 770 |
-
elem_classes="log-output"
|
| 771 |
-
)
|
| 772 |
-
analyze_btn.click(
|
| 773 |
-
fn=analyze,
|
| 774 |
-
inputs=[url_input, api_key, max_pages],
|
| 775 |
-
outputs=[analysis_output, logs_output],
|
| 776 |
-
)
|
| 777 |
-
return seo_tab
|
| 778 |
-
|
| 779 |
-
# ---- Auto Ad Generator UI as a function ----
|
| 780 |
-
def auto_ad_generator_ui():
|
| 781 |
-
openai.api_key = os.getenv("OPENAI_API_KEY")
|
| 782 |
-
|
| 783 |
-
def extract_text_from_url(url):
|
| 784 |
-
try:
|
| 785 |
-
resp = requests.get(url, timeout=30, headers={
|
| 786 |
-
"User-Agent": "Mozilla/5.0 (compatible; Bot/1.0)"
|
| 787 |
-
})
|
| 788 |
-
soup = BeautifulSoup(resp.content, "html.parser")
|
| 789 |
-
candidates = soup.find_all(['h1','h2','h3','h4','p','span','li'])
|
| 790 |
-
text = ' '.join([c.get_text(strip=True) for c in candidates])
|
| 791 |
-
text = text[:4000]
|
| 792 |
-
if len(text) < 100:
|
| 793 |
-
raise ValueError("Could not extract enough content (site may require JavaScript). Please enter keywords manually.")
|
| 794 |
-
return text
|
| 795 |
-
except Exception as e:
|
| 796 |
-
raise ValueError(f"URL extraction error: {e}")
|
| 797 |
-
|
| 798 |
-
def extract_keywords(text):
|
| 799 |
-
prompt = f"""
|
| 800 |
-
Extract up to 10 concise, relevant SEO keywords suitable for an automotive advertisement from the following content:
|
| 801 |
-
{text}
|
| 802 |
-
Keywords:
|
| 803 |
-
"""
|
| 804 |
-
response = openai.ChatCompletion.create(
|
| 805 |
-
model="gpt-4",
|
| 806 |
-
messages=[{"role": "user", "content": prompt}],
|
| 807 |
-
temperature=0.6,
|
| 808 |
-
max_tokens=100
|
| 809 |
-
)
|
| 810 |
-
output = response.choices[0].message.content.strip()
|
| 811 |
-
if ',' in output:
|
| 812 |
-
keywords = output.split(',')
|
| 813 |
-
else:
|
| 814 |
-
keywords = output.split('\n')
|
| 815 |
-
return [kw.strip() for kw in keywords if kw.strip()]
|
| 816 |
-
|
| 817 |
-
def generate_ad_copy(platform, keywords):
|
| 818 |
-
prompt = f"""
|
| 819 |
-
Create a compelling, SEO-optimized {platform} ad using these keywords: {', '.join(keywords)}.
|
| 820 |
-
Include a clear and enticing call-to-action.
|
| 821 |
-
"""
|
| 822 |
-
response = openai.ChatCompletion.create(
|
| 823 |
-
model="gpt-4",
|
| 824 |
-
messages=[{"role": "user", "content": prompt}],
|
| 825 |
-
temperature=0.7,
|
| 826 |
-
max_tokens=300
|
| 827 |
-
)
|
| 828 |
-
return response.choices[0].message.content.strip()
|
| 829 |
-
|
| 830 |
-
def generate_ad_image(keywords):
|
| 831 |
-
kw_str = ", ".join(keywords)
|
| 832 |
-
image_prompt = (
|
| 833 |
-
f"High-quality, photorealistic automotive ad photo of a luxury car. "
|
| 834 |
-
f"Clean background, professional lighting, stylish dealership setting. "
|
| 835 |
-
f"Keywords: {kw_str}. Room for text overlay, wide format, visually appealing."
|
| 836 |
-
)
|
| 837 |
-
response = openai.Image.create(
|
| 838 |
-
prompt=image_prompt,
|
| 839 |
-
n=1,
|
| 840 |
-
size="512x512"
|
| 841 |
-
)
|
| 842 |
-
image_url = response["data"][0]["url"]
|
| 843 |
-
img_data = requests.get(image_url).content
|
| 844 |
-
img_file = "generated_ad_image.png"
|
| 845 |
-
with open(img_file, "wb") as f:
|
| 846 |
-
f.write(img_data)
|
| 847 |
-
return img_file
|
| 848 |
-
|
| 849 |
-
def platform_html(platform, ad_text):
|
| 850 |
-
if platform == "Facebook":
|
| 851 |
-
color = "#1877F2"
|
| 852 |
-
icon = "🌐"
|
| 853 |
-
elif platform == "Instagram":
|
| 854 |
-
color = "linear-gradient(90deg, #f58529 0%, #dd2a7b 50%, #8134af 100%)"
|
| 855 |
-
icon = "📸"
|
| 856 |
-
elif platform == "X (Twitter)":
|
| 857 |
-
color = "#14171A"
|
| 858 |
-
icon = "🐦"
|
| 859 |
-
else: # Google Search
|
| 860 |
-
color = "#4285F4"
|
| 861 |
-
icon = "🔍"
|
| 862 |
-
|
| 863 |
-
if platform == "Instagram":
|
| 864 |
-
content = f"""
|
| 865 |
-
<div style="background: {color}; padding: 2px; border-radius: 12px; margin-bottom:16px;">
|
| 866 |
-
<div style="background: white; color: #333; padding: 18px 20px; border-radius: 10px;">
|
| 867 |
-
<span style="font-size: 1.5em;">{icon} <b>{platform}</b></span>
|
| 868 |
-
<div style="margin-top: 12px; font-size: 1.1em; line-height:1.6;">{ad_text}</div>
|
| 869 |
-
</div>
|
| 870 |
-
</div>
|
| 871 |
-
"""
|
| 872 |
-
else:
|
| 873 |
-
content = f"""
|
| 874 |
-
<div style="background: {color}; color: white; padding: 18px 20px; border-radius: 12px; margin-bottom:16px; min-height: 120px;">
|
| 875 |
-
<span style="font-size: 1.5em;">{icon} <b>{platform}</b></span>
|
| 876 |
-
<div style="margin-top: 12px; font-size: 1.1em; line-height:1.6;">{ad_text}</div>
|
| 877 |
-
</div>
|
| 878 |
-
"""
|
| 879 |
-
return content
|
| 880 |
-
|
| 881 |
-
def main_workflow(input_mode, url_or_keywords):
|
| 882 |
-
error = None
|
| 883 |
-
keywords = []
|
| 884 |
-
ad_copies = {}
|
| 885 |
-
image_path = None
|
| 886 |
-
|
| 887 |
-
if input_mode == "URL":
|
| 888 |
-
try:
|
| 889 |
-
text = extract_text_from_url(url_or_keywords)
|
| 890 |
-
keywords = extract_keywords(text)
|
| 891 |
-
except Exception as e:
|
| 892 |
-
return None, None, None, f"{e}"
|
| 893 |
-
else:
|
| 894 |
-
keywords = [kw.strip() for kw in url_or_keywords.split(",") if kw.strip()]
|
| 895 |
-
if not keywords:
|
| 896 |
-
return None, None, None, "Please provide at least one keyword."
|
| 897 |
-
# Generate ad copies
|
| 898 |
-
platforms = ["Facebook", "Instagram", "X (Twitter)", "Google Search"]
|
| 899 |
-
for platform in platforms:
|
| 900 |
-
ad_copies[platform] = generate_ad_copy(platform, keywords)
|
| 901 |
-
# Generate image
|
| 902 |
-
try:
|
| 903 |
-
image_path = generate_ad_image(keywords)
|
| 904 |
-
except Exception as e:
|
| 905 |
-
error = f"Image generation error: {e}"
|
| 906 |
-
|
| 907 |
-
# Save ads to txt
|
| 908 |
-
output_txt = "generated_ads.txt"
|
| 909 |
-
with open(output_txt, "w", encoding="utf-8") as f:
|
| 910 |
-
for platform, content in ad_copies.items():
|
| 911 |
-
f.write(f"--- {platform} Ad Copy ---\n{content}\n\n")
|
| 912 |
-
return keywords, ad_copies, image_path, error
|
| 913 |
-
|
| 914 |
-
def run_space(input_mode, url, keywords):
|
| 915 |
-
url_or_keywords = url if input_mode == "URL" else keywords
|
| 916 |
-
keywords, ad_copies, image_path, error = main_workflow(input_mode, url_or_keywords)
|
| 917 |
-
ad_previews = ""
|
| 918 |
-
if ad_copies:
|
| 919 |
-
for platform, ad in ad_copies.items():
|
| 920 |
-
ad_previews += platform_html(platform, ad)
|
| 921 |
-
return (
|
| 922 |
-
keywords,
|
| 923 |
-
ad_previews,
|
| 924 |
-
image_path,
|
| 925 |
-
"generated_ads.txt" if ad_copies else None,
|
| 926 |
-
error
|
| 927 |
-
)
|
| 928 |
-
|
| 929 |
-
with gr.Blocks() as ad_tab:
|
| 930 |
-
gr.Markdown("# 🚗 Auto Ad Generator\nPaste a car listing URL **or** enter your own keywords, then preview AI-generated ads for each social media platform, plus an auto-generated image!")
|
| 931 |
-
input_mode = gr.Radio(["URL", "Keywords"], value="URL", label="Input Type")
|
| 932 |
-
url_input = gr.Textbox(label="Listing URL", placeholder="https://www.cars.com/listing/...", visible=True)
|
| 933 |
-
kw_input = gr.Textbox(label="Manual Keywords (comma separated)", placeholder="e.g. BMW, used car, sunroof", visible=False)
|
| 934 |
-
submit_btn = gr.Button("Generate Ads")
|
| 935 |
-
|
| 936 |
-
gr.Markdown("## Keywords")
|
| 937 |
-
kw_out = gr.JSON(label="Extracted/Provided Keywords")
|
| 938 |
-
|
| 939 |
-
gr.Markdown("## Ad Copy Previews")
|
| 940 |
-
ad_out = gr.HTML(label="Ad Copy Preview")
|
| 941 |
-
|
| 942 |
-
gr.Markdown("## Generated Ad Image")
|
| 943 |
-
img_out = gr.Image(label="Generated Ad Image", type="filepath")
|
| 944 |
-
|
| 945 |
-
gr.Markdown("## Download Ad Copies")
|
| 946 |
-
file_out = gr.File(label="Download TXT")
|
| 947 |
-
|
| 948 |
-
err_out = gr.Textbox(label="Errors", interactive=False)
|
| 949 |
-
|
| 950 |
-
def show_hide_fields(choice):
|
| 951 |
-
return (
|
| 952 |
-
gr.update(visible=choice == "URL"),
|
| 953 |
-
gr.update(visible=choice == "Keywords"),
|
| 954 |
-
)
|
| 955 |
-
|
| 956 |
-
input_mode.change(show_hide_fields, input_mode, [url_input, kw_input])
|
| 957 |
-
|
| 958 |
-
submit_btn.click(
|
| 959 |
-
run_space,
|
| 960 |
-
inputs=[input_mode, url_input, kw_input],
|
| 961 |
-
outputs=[kw_out, ad_out, img_out, file_out, err_out]
|
| 962 |
-
)
|
| 963 |
-
return ad_tab
|
| 964 |
-
|
| 965 |
-
# ---- Main App: Two Tabs ----
|
| 966 |
if __name__ == "__main__":
|
|
|
|
| 967 |
os.makedirs(config.STORAGE_PATH, exist_ok=True)
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
auto_ad_generator_ui()
|
| 973 |
-
demo.launch(
|
| 974 |
share=False,
|
| 975 |
server_name="0.0.0.0",
|
| 976 |
show_api=False,
|
| 977 |
show_error=True,
|
| 978 |
-
)
|
|
|
|
| 1 |
"""
|
| 2 |
+
SEO Analyzer UI using Gradio, Web Crawler, and OpenAI
|
| 3 |
"""
|
| 4 |
+
|
| 5 |
import gradio as gr
|
| 6 |
import logging
|
| 7 |
import json
|
|
|
|
| 19 |
from datetime import datetime
|
| 20 |
import tempfile
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
from crawler import Crawler
|
| 23 |
from frontier import URLFrontier
|
| 24 |
from models import URL, Page
|
|
|
|
| 28 |
|
| 29 |
load_dotenv(find_dotenv())
|
| 30 |
|
| 31 |
+
# Check if we're in deployment mode (e.g., Hugging Face Spaces)
|
| 32 |
IS_DEPLOYMENT = os.getenv('DEPLOYMENT', 'false').lower() == 'true'
|
| 33 |
|
| 34 |
# Custom CSS for better styling
|
|
|
|
| 38 |
margin: auto;
|
| 39 |
padding: 20px;
|
| 40 |
}
|
|
|
|
| 41 |
.header {
|
| 42 |
text-align: center;
|
| 43 |
margin-bottom: 2rem;
|
| 44 |
}
|
|
|
|
| 45 |
.header h1 {
|
| 46 |
color: #2d3748;
|
| 47 |
font-size: 2.5rem;
|
| 48 |
font-weight: 700;
|
| 49 |
margin-bottom: 1rem;
|
| 50 |
}
|
|
|
|
| 51 |
.header p {
|
| 52 |
color: #4a5568;
|
| 53 |
font-size: 1.1rem;
|
| 54 |
max-width: 800px;
|
| 55 |
margin: 0 auto;
|
| 56 |
}
|
|
|
|
| 57 |
.input-section {
|
| 58 |
background: #f7fafc;
|
| 59 |
border-radius: 12px;
|
|
|
|
| 61 |
margin-bottom: 24px;
|
| 62 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 63 |
}
|
|
|
|
| 64 |
.analysis-section {
|
| 65 |
background: white;
|
| 66 |
border-radius: 12px;
|
|
|
|
| 68 |
margin-top: 24px;
|
| 69 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 70 |
}
|
|
|
|
| 71 |
.log-section {
|
| 72 |
font-family: monospace;
|
| 73 |
background: #1a202c;
|
|
|
|
| 76 |
border-radius: 8px;
|
| 77 |
margin-top: 24px;
|
| 78 |
}
|
|
|
|
| 79 |
/* Custom styling for inputs */
|
| 80 |
.input-container {
|
| 81 |
background: white;
|
|
|
|
| 83 |
border-radius: 8px;
|
| 84 |
margin-bottom: 16px;
|
| 85 |
}
|
|
|
|
| 86 |
/* Custom styling for the slider */
|
| 87 |
.slider-container {
|
| 88 |
padding: 12px;
|
| 89 |
background: white;
|
| 90 |
border-radius: 8px;
|
| 91 |
}
|
|
|
|
| 92 |
/* Custom styling for buttons */
|
| 93 |
.primary-button {
|
| 94 |
background: #4299e1 !important;
|
|
|
|
| 98 |
font-weight: 600 !important;
|
| 99 |
transition: all 0.3s ease !important;
|
| 100 |
}
|
|
|
|
| 101 |
.primary-button:hover {
|
| 102 |
background: #3182ce !important;
|
| 103 |
transform: translateY(-1px) !important;
|
| 104 |
}
|
|
|
|
| 105 |
/* Markdown output styling */
|
| 106 |
.markdown-output {
|
| 107 |
font-family: system-ui, -apple-system, sans-serif;
|
| 108 |
line-height: 1.6;
|
| 109 |
}
|
|
|
|
| 110 |
.markdown-output h1 {
|
| 111 |
color: #2d3748;
|
| 112 |
border-bottom: 2px solid #e2e8f0;
|
| 113 |
padding-bottom: 0.5rem;
|
| 114 |
}
|
|
|
|
| 115 |
.markdown-output h2 {
|
| 116 |
color: #4a5568;
|
| 117 |
margin-top: 2rem;
|
| 118 |
}
|
|
|
|
| 119 |
.markdown-output h3 {
|
| 120 |
color: #718096;
|
| 121 |
margin-top: 1.5rem;
|
| 122 |
}
|
|
|
|
| 123 |
/* Progress bar styling */
|
| 124 |
.progress-bar {
|
| 125 |
height: 8px !important;
|
| 126 |
border-radius: 4px !important;
|
| 127 |
background: #ebf8ff !important;
|
| 128 |
}
|
|
|
|
| 129 |
.progress-bar-fill {
|
| 130 |
background: #4299e1 !important;
|
| 131 |
border-radius: 4px !important;
|
| 132 |
}
|
|
|
|
| 133 |
/* Add some spacing between sections */
|
| 134 |
.gap {
|
| 135 |
margin: 2rem 0;
|
|
|
|
| 416 |
# Format the results
|
| 417 |
formatted_analysis = f"""
|
| 418 |
# SEO Analysis Report for {domain}
|
|
|
|
| 419 |
## Overall Analysis
|
| 420 |
{overall_analysis}
|
|
|
|
| 421 |
## Page-Specific Analyses
|
| 422 |
"""
|
| 423 |
for page_analysis in page_analyses:
|
|
|
|
| 474 |
# Create analysis prompt
|
| 475 |
prompt = f"""
|
| 476 |
You are an expert SEO consultant. Analyze this website's SEO based on the crawled data:
|
|
|
|
| 477 |
{json.dumps(site_overview, indent=2)}
|
|
|
|
| 478 |
Provide a comprehensive SEO analysis including:
|
| 479 |
1. Overall site structure and navigation
|
| 480 |
2. Common SEO issues across pages
|
| 481 |
3. Content quality and optimization
|
| 482 |
4. Technical SEO recommendations
|
| 483 |
5. Priority improvements
|
|
|
|
| 484 |
Format your response in Markdown.
|
| 485 |
"""
|
| 486 |
|
|
|
|
| 510 |
# Create page analysis prompt
|
| 511 |
prompt = f"""
|
| 512 |
Analyze this page's SEO:
|
|
|
|
| 513 |
URL: {page['url']}
|
| 514 |
Metadata: {json.dumps(page['metadata'], indent=2)}
|
|
|
|
| 515 |
Provide specific recommendations for:
|
| 516 |
1. Title and meta description
|
| 517 |
2. Heading structure
|
| 518 |
3. Content optimization
|
| 519 |
4. Internal linking
|
| 520 |
5. Technical improvements
|
|
|
|
| 521 |
Format your response in Markdown.
|
| 522 |
"""
|
| 523 |
|
|
|
|
| 583 |
# Create markdown content for the about section
|
| 584 |
about_markdown = """
|
| 585 |
# 🔍 SEO Analyzer Pro
|
|
|
|
| 586 |
Analyze your website's SEO performance using advanced crawling and AI technology.
|
| 587 |
|
| 588 |
### Features:
|
|
|
|
| 668 |
|
| 669 |
return iface
|
| 670 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
if __name__ == "__main__":
|
| 672 |
+
# Create base storage directory if it doesn't exist
|
| 673 |
os.makedirs(config.STORAGE_PATH, exist_ok=True)
|
| 674 |
+
|
| 675 |
+
# Create and launch UI
|
| 676 |
+
ui = create_ui()
|
| 677 |
+
ui.launch(
|
|
|
|
|
|
|
| 678 |
share=False,
|
| 679 |
server_name="0.0.0.0",
|
| 680 |
show_api=False,
|
| 681 |
show_error=True,
|
| 682 |
+
)
|