Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,60 +1,22 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import asyncio
|
| 3 |
import os
|
| 4 |
-
import json
|
| 5 |
from datetime import datetime
|
| 6 |
|
| 7 |
from seo_analyzer import run_seo_analysis_fastapi
|
| 8 |
from ai_visibility import run_ai_visibility_analysis
|
| 9 |
|
| 10 |
-
# Helper
|
| 11 |
-
def run_async(coro):
|
| 12 |
-
try:
|
| 13 |
-
loop = asyncio.get_running_loop()
|
| 14 |
-
except RuntimeError:
|
| 15 |
-
loop = None
|
| 16 |
-
if loop and loop.is_running():
|
| 17 |
-
# If already in an event loop (e.g., Gradio's async environment), create a new task
|
| 18 |
-
import nest_asyncio
|
| 19 |
-
nest_asyncio.apply()
|
| 20 |
-
return asyncio.run(coro)
|
| 21 |
-
else:
|
| 22 |
-
return asyncio.run(coro)
|
| 23 |
-
|
| 24 |
-
def analyze_seo(url, max_pages, max_concurrent, use_ai):
|
| 25 |
-
"""Wrapper for SEO analysis."""
|
| 26 |
-
result = run_async(run_seo_analysis_fastapi(
|
| 27 |
-
base_url=url,
|
| 28 |
-
max_pages=int(max_pages),
|
| 29 |
-
use_ai=use_ai,
|
| 30 |
-
max_concurrent=int(max_concurrent)
|
| 31 |
-
))
|
| 32 |
-
# result is a tuple (data, csv_path) or error dict
|
| 33 |
-
return format_seo_result(result)
|
| 34 |
-
|
| 35 |
-
def analyze_ai_visibility(url, max_pages, max_concurrent, use_ai):
|
| 36 |
-
"""Wrapper for AI Visibility analysis."""
|
| 37 |
-
result = run_async(run_ai_visibility_analysis(
|
| 38 |
-
base_url=url,
|
| 39 |
-
max_pages=int(max_pages),
|
| 40 |
-
max_concurrent=int(max_concurrent),
|
| 41 |
-
use_ai=use_ai
|
| 42 |
-
))
|
| 43 |
-
return format_ai_result(result)
|
| 44 |
-
|
| 45 |
def format_seo_result(result):
|
| 46 |
if isinstance(result, tuple):
|
| 47 |
data, csv_path = result
|
| 48 |
-
# data is list of page dicts
|
| 49 |
if not data:
|
| 50 |
return "No data returned."
|
| 51 |
output = f"## SEO Analysis Results\n"
|
| 52 |
output += f"**Pages analyzed:** {len(data)}\n"
|
| 53 |
-
# summarize overall score
|
| 54 |
scores = [p.get('seo_score', 0) for p in data]
|
| 55 |
avg = sum(scores) / len(scores) if scores else 0
|
| 56 |
output += f"**Average SEO Score:** {avg:.1f}/100\n\n"
|
| 57 |
-
# show each page
|
| 58 |
for i, page in enumerate(data, 1):
|
| 59 |
output += f"### Page {i}: {page.get('url', '')}\n"
|
| 60 |
output += f"- Score: {page.get('seo_score', 0)}/100\n"
|
|
@@ -68,7 +30,6 @@ def format_seo_result(result):
|
|
| 68 |
output += "\n"
|
| 69 |
return output
|
| 70 |
else:
|
| 71 |
-
# error dict
|
| 72 |
return f"❌ Error: {result.get('message', 'Unknown error')}"
|
| 73 |
|
| 74 |
def format_ai_result(result):
|
|
@@ -79,14 +40,12 @@ def format_ai_result(result):
|
|
| 79 |
output += f"**Pages analyzed:** {result.get('pages_analyzed', 0)}\n"
|
| 80 |
output += f"**Overall AI Readiness Score:** {result.get('ai_readiness_score', 0)}/100\n"
|
| 81 |
output += f"**Page types detected:** {result.get('page_type_breakdown', {})}\n\n"
|
| 82 |
-
# Category scores
|
| 83 |
cat_scores = result.get('category_scores', {})
|
| 84 |
if cat_scores:
|
| 85 |
output += "### Category Scores\n"
|
| 86 |
for k, v in cat_scores.items():
|
| 87 |
output += f"- {k.replace('_score', '').replace('_', ' ').title()}: {v if v is not None else 'N/A'}\n"
|
| 88 |
output += "\n"
|
| 89 |
-
# Per-page details
|
| 90 |
previews = result.get('results_preview', [])
|
| 91 |
if previews:
|
| 92 |
output += "### Per-Page Details\n"
|
|
@@ -100,7 +59,26 @@ def format_ai_result(result):
|
|
| 100 |
output += f"- Freshness: {p.get('freshness_status', 'unknown')}\n\n"
|
| 101 |
return output
|
| 102 |
|
| 103 |
-
# Gradio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
with gr.Blocks(title="SEO & AI Visibility Analyzer") as demo:
|
| 105 |
gr.Markdown("# 🚀 SEO & AI Visibility Analysis Tool")
|
| 106 |
gr.Markdown("Enter a website URL to analyze its SEO health and AI search readiness.")
|
|
@@ -122,12 +100,12 @@ with gr.Blocks(title="SEO & AI Visibility Analyzer") as demo:
|
|
| 122 |
output = gr.Markdown(label="Results")
|
| 123 |
|
| 124 |
seo_btn.click(
|
| 125 |
-
fn=
|
| 126 |
inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check],
|
| 127 |
outputs=output
|
| 128 |
)
|
| 129 |
ai_btn.click(
|
| 130 |
-
fn=
|
| 131 |
inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check],
|
| 132 |
outputs=output
|
| 133 |
)
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import asyncio
|
| 3 |
import os
|
|
|
|
| 4 |
from datetime import datetime
|
| 5 |
|
| 6 |
from seo_analyzer import run_seo_analysis_fastapi
|
| 7 |
from ai_visibility import run_ai_visibility_analysis
|
| 8 |
|
| 9 |
+
# ---- Helper functions to format output ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
def format_seo_result(result):
|
| 11 |
if isinstance(result, tuple):
|
| 12 |
data, csv_path = result
|
|
|
|
| 13 |
if not data:
|
| 14 |
return "No data returned."
|
| 15 |
output = f"## SEO Analysis Results\n"
|
| 16 |
output += f"**Pages analyzed:** {len(data)}\n"
|
|
|
|
| 17 |
scores = [p.get('seo_score', 0) for p in data]
|
| 18 |
avg = sum(scores) / len(scores) if scores else 0
|
| 19 |
output += f"**Average SEO Score:** {avg:.1f}/100\n\n"
|
|
|
|
| 20 |
for i, page in enumerate(data, 1):
|
| 21 |
output += f"### Page {i}: {page.get('url', '')}\n"
|
| 22 |
output += f"- Score: {page.get('seo_score', 0)}/100\n"
|
|
|
|
| 30 |
output += "\n"
|
| 31 |
return output
|
| 32 |
else:
|
|
|
|
| 33 |
return f"❌ Error: {result.get('message', 'Unknown error')}"
|
| 34 |
|
| 35 |
def format_ai_result(result):
|
|
|
|
| 40 |
output += f"**Pages analyzed:** {result.get('pages_analyzed', 0)}\n"
|
| 41 |
output += f"**Overall AI Readiness Score:** {result.get('ai_readiness_score', 0)}/100\n"
|
| 42 |
output += f"**Page types detected:** {result.get('page_type_breakdown', {})}\n\n"
|
|
|
|
| 43 |
cat_scores = result.get('category_scores', {})
|
| 44 |
if cat_scores:
|
| 45 |
output += "### Category Scores\n"
|
| 46 |
for k, v in cat_scores.items():
|
| 47 |
output += f"- {k.replace('_score', '').replace('_', ' ').title()}: {v if v is not None else 'N/A'}\n"
|
| 48 |
output += "\n"
|
|
|
|
| 49 |
previews = result.get('results_preview', [])
|
| 50 |
if previews:
|
| 51 |
output += "### Per-Page Details\n"
|
|
|
|
| 59 |
output += f"- Freshness: {p.get('freshness_status', 'unknown')}\n\n"
|
| 60 |
return output
|
| 61 |
|
| 62 |
+
# ---- Async analysis wrappers (Gradio will handle async functions) ----
|
| 63 |
+
async def analyze_seo_async(url, max_pages, max_concurrent, use_ai):
|
| 64 |
+
result = await run_seo_analysis_fastapi(
|
| 65 |
+
base_url=url,
|
| 66 |
+
max_pages=int(max_pages),
|
| 67 |
+
use_ai=use_ai,
|
| 68 |
+
max_concurrent=int(max_concurrent)
|
| 69 |
+
)
|
| 70 |
+
return format_seo_result(result)
|
| 71 |
+
|
| 72 |
+
async def analyze_ai_async(url, max_pages, max_concurrent, use_ai):
|
| 73 |
+
result = await run_ai_visibility_analysis(
|
| 74 |
+
base_url=url,
|
| 75 |
+
max_pages=int(max_pages),
|
| 76 |
+
max_concurrent=int(max_concurrent),
|
| 77 |
+
use_ai=use_ai
|
| 78 |
+
)
|
| 79 |
+
return format_ai_result(result)
|
| 80 |
+
|
| 81 |
+
# ---- Gradio Interface ----
|
| 82 |
with gr.Blocks(title="SEO & AI Visibility Analyzer") as demo:
|
| 83 |
gr.Markdown("# 🚀 SEO & AI Visibility Analysis Tool")
|
| 84 |
gr.Markdown("Enter a website URL to analyze its SEO health and AI search readiness.")
|
|
|
|
| 100 |
output = gr.Markdown(label="Results")
|
| 101 |
|
| 102 |
seo_btn.click(
|
| 103 |
+
fn=analyze_seo_async,
|
| 104 |
inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check],
|
| 105 |
outputs=output
|
| 106 |
)
|
| 107 |
ai_btn.click(
|
| 108 |
+
fn=analyze_ai_async,
|
| 109 |
inputs=[url_input, max_pages_input, max_concurrent_input, use_ai_check],
|
| 110 |
outputs=output
|
| 111 |
)
|