import gradio as gr import asyncio import subprocess import sys import os from datetime import datetime os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "/home/user/.cache/ms-playwright") def install_playwright_browsers(): marker = os.path.join(os.environ["PLAYWRIGHT_BROWSERS_PATH"], ".installed") if os.path.exists(marker): print("✅ Playwright browsers already installed, skipping.") return try: print("📦 Installing Playwright browsers...") subprocess.check_call( [sys.executable, "-m", "playwright", "install", "chromium"], ) os.makedirs(os.environ["PLAYWRIGHT_BROWSERS_PATH"], exist_ok=True) open(marker, "w").close() print("✅ Playwright browsers installed successfully.") except subprocess.CalledProcessError as e: print(f"❌ Playwright install failed with code {e.returncode}") except Exception as e: print(f"❌ Unexpected error during install: {e}") install_playwright_browsers() try: import spaces HAS_SPACES = True except ImportError: HAS_SPACES = False if HAS_SPACES: @spaces.GPU def dummy_gpu(): pass from seo_analyzer import run_seo_analysis_fastapi from ai_visibility import run_ai_visibility_analysis # ---- Helper functions to format output ---- def format_seo_result(result): if isinstance(result, tuple): data, csv_path = result if not data: return "No data returned." output = f"## SEO Analysis Results\n" output += f"**Pages analyzed:** {len(data)}\n" scores = [p.get('seo_score', 0) for p in data] avg = sum(scores) / len(scores) if scores else 0 output += f"**Average SEO Score:** {avg:.1f}/100\n\n" # Generate detailed strengths and issues based on page metrics detailed_strengths = [] detailed_issues = [] for page in data: url = page.get('url', '') # Title title_len = len(page.get('title', '')) if 50 <= title_len <= 60: detailed_strengths.append(f"- Strong title length ({title_len} chars) on {url}") elif title_len < 30 or title_len > 70: detailed_issues.append(f"- Title too {'short' if title_len < 30 else 'long'} ({title_len} chars) on {url}") # Meta description meta_len = len(page.get('meta_description', '')) if 120 <= meta_len <= 160: detailed_strengths.append(f"- Good meta description length ({meta_len} chars) on {url}") elif meta_len > 0 and (meta_len < 70 or meta_len > 170): detailed_issues.append(f"- Meta description length ({meta_len} chars) suboptimal on {url}") # H1 h1 = page.get('h1_count', 0) if h1 == 1: detailed_strengths.append(f"- Exactly one H1 on {url}") elif h1 == 0: detailed_issues.append(f"- Missing H1 on {url}") elif h1 > 1: detailed_issues.append(f"- Multiple H1s ({h1}) on {url}") # Word count wc = page.get('word_count', 0) if wc >= 800: detailed_strengths.append(f"- Good word count ({wc}) on {url}") elif wc < 300: detailed_issues.append(f"- Low word count ({wc}) on {url}") # Alt tags total_img = page.get('total_images', 0) missing_alt = page.get('missing_alt_tags', 0) if total_img > 0 and missing_alt == 0: detailed_strengths.append(f"- All images have alt text on {url}") elif total_img > 0 and missing_alt > 0: detailed_issues.append(f"- {missing_alt} images missing alt text on {url}") # Schema schema = page.get('schema_types', '') if schema and schema != "No schema found": detailed_strengths.append(f"- Schema detected ({schema}) on {url}") else: detailed_issues.append(f"- No schema found on {url}") # Readability readability = page.get('readability_score', 0) if readability >= 50: detailed_strengths.append(f"- Good readability score ({readability}) on {url}") elif readability < 30: detailed_issues.append(f"- Poor readability ({readability}) on {url}") if detailed_strengths: output += "### SEO Strengths (detailed)\n" output += "\n".join(detailed_strengths) + "\n\n" if detailed_issues: output += "### SEO Issues (detailed)\n" output += "\n".join(detailed_issues) + "\n\n" # Per-page data for i, page in enumerate(data, 1): output += f"### Page {i}: {page.get('url', '')}\n" output += f"- Score: {page.get('seo_score', 0)}/100\n" output += f"- Title: {page.get('title', 'No title')}\n" output += f"- Word Count: {page.get('word_count', 0)}\n" output += f"- H1: {page.get('h1_count', 0)}, H2: {page.get('h2_count', 0)}, H3: {page.get('h3_count', 0)}\n" output += f"- Images: {page.get('total_images', 0)} (missing alt: {page.get('missing_alt_tags', 0)})\n" output += f"- Internal/External links: {page.get('internal_links', 0)}/{page.get('external_links', 0)}\n" output += f"- Readability: {page.get('readability_score', 0)}\n" output += f"- Grammar Errors: {page.get('grammar_errors', 0)}\n" output += f"- Canonical Tag: {'Yes' if page.get('canonical_tag') else 'No'}\n" output += f"- OpenGraph Tags: {page.get('opengraph_tags', 0)}\n" output += f"- Twitter Cards: {page.get('twitter_tags', 0)}\n" output += f"- Robots Meta: {page.get('robots_meta', 'none')}\n" output += f"- Viewport: {'Yes' if page.get('viewport_present') else 'No'}\n" output += f"- Schema Types: {page.get('schema_types', 'none')}\n" output += f"- Text/HTML Ratio: {page.get('text_to_html_ratio', 0)}%\n" output += f"- Load Time: {page.get('load_time', 0)}s\n" output += f"- Meta Description: {page.get('meta_description', '')}\n" output += f"- Heading Order: {page.get('heading_order', '')}\n" if page.get('ai_suggestions'): output += f"- AI Suggestions: {page['ai_suggestions'][:200]}...\n" output += "\n" return output else: return f"❌ Error: {result.get('message', 'Unknown error')}" def format_ai_result(result): if result.get('status') == 'error': return f"❌ Error: {result.get('message', 'Unknown error')}" output = f"## AI Visibility / Readiness Analysis\n" output += f"**URL:** {result.get('url', '')}\n" output += f"**Pages analyzed:** {result.get('pages_analyzed', 0)}\n" output += f"**Overall AI Readiness Score:** {result.get('ai_readiness_score', 0)}/100\n" output += f"**Page types detected:** {result.get('page_type_breakdown', {})}\n\n" cat_scores = result.get('category_scores', {}) if cat_scores: output += "### Category Scores\n" for k, v in cat_scores.items(): output += f"- {k.replace('_score', '').replace('_', ' ').title()}: {v if v is not None else 'N/A'}\n" output += "\n" previews = result.get('results_preview', []) if previews: output += "### Per-Page Details\n" for p in previews: output += f"**URL:** {p.get('url', '')}\n" output += f"- Page Type: {p.get('page_type', 'unknown')} (conf: {p.get('page_type_confidence', 0):.2f})\n" output += f"- Readiness Score: {p.get('ai_readiness_score', 0)}/100\n" output += f"- Topic Clarity: {p.get('topic_clarity', 0)}\n" output += f"- Content Completeness: {p.get('content_completeness', 0)}\n" output += f"- Entity Clarity: {p.get('entity_clarity', 'N/A')}\n" output += f"- Freshness: {p.get('freshness_status', 'unknown')}\n\n" # Detailed issues and strengths (per page) from backend issues = result.get('issues', []) strengths = result.get('strengths', []) if issues: output += "### Detailed Issues (per page)\n" for issue in issues: output += f"- {issue.get('title')} (Severity: {issue.get('severity')}) on {issue.get('page')}\n" output += f" Explanation: {issue.get('explanation')}\n" if issue.get('recommended_fix'): output += f" Fix: {issue.get('recommended_fix')}\n" if strengths: output += "### Detailed Strengths (per page)\n" for strength in strengths: output += f"- {strength.get('title')} on {strength.get('page')}\n" output += f" Detail: {strength.get('detail')}\n" return output # ---- Async analysis wrappers ---- async def analyze_seo_async(url, max_pages, max_concurrent, use_ai): result = await run_seo_analysis_fastapi( base_url=url, max_pages=int(max_pages), use_ai=use_ai, max_concurrent=int(max_concurrent) ) return format_seo_result(result) async def analyze_ai_async(url, max_pages, max_concurrent, use_ai): result = await run_ai_visibility_analysis( base_url=url, max_pages=int(max_pages), max_concurrent=int(max_concurrent), use_ai=use_ai ) return format_ai_result(result) # ---- Gradio Interface ---- with gr.Blocks(title="SEO & AI Visibility Analyzer") as demo: gr.Markdown("# 🚀 SEO & AI Visibility Analysis Tool") gr.Markdown("Enter a website URL to analyze its SEO health and AI search readiness.") with gr.Row(): with gr.Column(scale=2): url_input = gr.Textbox(label="Website URL", placeholder="https://example.com", value="https://example.com") with gr.Column(scale=1): max_pages_input = gr.Number(label="Max Pages", value=3, minimum=1, maximum=20, step=1) with gr.Column(scale=1): max_concurrent_input = gr.Number(label="Concurrent Browsers", value=1, minimum=1, maximum=5, step=1) with gr.Column(scale=1): use_ai_check = gr.Checkbox(label="Enable AI Suggestions", value=True) with gr.Row(): seo_btn = gr.Button("🔍 Analyze SEO", variant="primary") ai_btn = gr.Button("🤖 Analyze AI Visibility", variant="secondary") output = gr.Markdown(label="Results") seo_btn.click( fn=analyze_seo_async, inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check], outputs=output ) ai_btn.click( fn=analyze_ai_async, inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check], outputs=output ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860) # import gradio as gr # import asyncio # import subprocess # import sys # import os # from datetime import datetime # os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "/home/user/.cache/ms-playwright") # def install_playwright_browsers(): # """Install Playwright Chromium browsers – works on Hugging Face Spaces.""" # marker = os.path.join(os.environ["PLAYWRIGHT_BROWSERS_PATH"], ".installed") # if os.path.exists(marker): # print("✅ Playwright browsers already installed, skipping.") # return # try: # print("📦 Installing Playwright browsers...") # subprocess.check_call( # [sys.executable, "-m", "playwright", "install", "chromium"], # ) # os.makedirs(os.environ["PLAYWRIGHT_BROWSERS_PATH"], exist_ok=True) # open(marker, "w").close() # print("✅ Playwright browsers installed successfully.") # except subprocess.CalledProcessError as e: # print(f"❌ Playwright install failed with code {e.returncode}") # except Exception as e: # print(f"❌ Unexpected error during install: {e}") # install_playwright_browsers() # try: # import spaces # HAS_SPACES = True # except ImportError: # HAS_SPACES = False # if HAS_SPACES: # @spaces.GPU # def dummy_gpu(): # pass # This makes ZeroGPU happy # from seo_analyzer import run_seo_analysis_fastapi # from ai_visibility import run_ai_visibility_analysis # # ---- Helper functions to format output ---- # def format_seo_result(result): # if isinstance(result, tuple): # data, csv_path = result # if not data: # return "No data returned." # output = f"## SEO Analysis Results\n" # output += f"**Pages analyzed:** {len(data)}\n" # scores = [p.get('seo_score', 0) for p in data] # avg = sum(scores) / len(scores) if scores else 0 # output += f"**Average SEO Score:** {avg:.1f}/100\n\n" # for i, page in enumerate(data, 1): # output += f"### Page {i}: {page.get('url', '')}\n" # output += f"- Score: {page.get('seo_score', 0)}/100\n" # output += f"- Title: {page.get('title', 'No title')}\n" # output += f"- Word Count: {page.get('word_count', 0)}\n" # output += f"- H1: {page.get('h1_count', 0)}, H2: {page.get('h2_count', 0)}, H3: {page.get('h3_count', 0)}\n" # output += f"- Images: {page.get('total_images', 0)} (missing alt: {page.get('missing_alt_tags', 0)})\n" # output += f"- Internal/External links: {page.get('internal_links', 0)}/{page.get('external_links', 0)}\n" # if page.get('ai_suggestions'): # output += f"- AI Suggestions: {page['ai_suggestions'][:200]}...\n" # output += "\n" # return output # else: # return f"❌ Error: {result.get('message', 'Unknown error')}" # def format_ai_result(result): # if result.get('status') == 'error': # return f"❌ Error: {result.get('message', 'Unknown error')}" # output = f"## AI Visibility / Readiness Analysis\n" # output += f"**URL:** {result.get('url', '')}\n" # output += f"**Pages analyzed:** {result.get('pages_analyzed', 0)}\n" # output += f"**Overall AI Readiness Score:** {result.get('ai_readiness_score', 0)}/100\n" # output += f"**Page types detected:** {result.get('page_type_breakdown', {})}\n\n" # cat_scores = result.get('category_scores', {}) # if cat_scores: # output += "### Category Scores\n" # for k, v in cat_scores.items(): # output += f"- {k.replace('_score', '').replace('_', ' ').title()}: {v if v is not None else 'N/A'}\n" # output += "\n" # previews = result.get('results_preview', []) # if previews: # output += "### Per-Page Details\n" # for p in previews: # output += f"**URL:** {p.get('url', '')}\n" # output += f"- Page Type: {p.get('page_type', 'unknown')} (conf: {p.get('page_type_confidence', 0):.2f})\n" # output += f"- Readiness Score: {p.get('ai_readiness_score', 0)}/100\n" # output += f"- Topic Clarity: {p.get('topic_clarity', 0)}\n" # output += f"- Content Completeness: {p.get('content_completeness', 0)}\n" # output += f"- Entity Clarity: {p.get('entity_clarity', 'N/A')}\n" # output += f"- Freshness: {p.get('freshness_status', 'unknown')}\n\n" # return output # # ---- Async analysis wrappers (Gradio will handle async functions) ---- # async def analyze_seo_async(url, max_pages, max_concurrent, use_ai): # result = await run_seo_analysis_fastapi( # base_url=url, # max_pages=int(max_pages), # use_ai=use_ai, # max_concurrent=int(max_concurrent) # ) # return format_seo_result(result) # async def analyze_ai_async(url, max_pages, max_concurrent, use_ai): # result = await run_ai_visibility_analysis( # base_url=url, # max_pages=int(max_pages), # max_concurrent=int(max_concurrent), # use_ai=use_ai # ) # return format_ai_result(result) # # ---- Gradio Interface ---- # with gr.Blocks(title="SEO & AI Visibility Analyzer") as demo: # gr.Markdown("# 🚀 SEO & AI Visibility Analysis Tool") # gr.Markdown("Enter a website URL to analyze its SEO health and AI search readiness.") # with gr.Row(): # with gr.Column(scale=2): # url_input = gr.Textbox(label="Website URL", placeholder="https://example.com", value="https://example.com") # with gr.Column(scale=1): # max_pages_input = gr.Number(label="Max Pages", value=3, minimum=1, maximum=20, step=1) # with gr.Column(scale=1): # max_concurrent_input = gr.Number(label="Concurrent Browsers", value=1, minimum=1, maximum=5, step=1) # with gr.Column(scale=1): # use_ai_check = gr.Checkbox(label="Enable AI Suggestions", value=True) # with gr.Row(): # seo_btn = gr.Button("🔍 Analyze SEO", variant="primary") # ai_btn = gr.Button("🤖 Analyze AI Visibility", variant="secondary") # output = gr.Markdown(label="Results") # seo_btn.click( # fn=analyze_seo_async, # inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check], # outputs=output # ) # ai_btn.click( # fn=analyze_ai_async, # inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check], # outputs=output # ) # if __name__ == "__main__": # demo.launch(server_name="0.0.0.0", server_port=7860)