284 lines
9.7 KiB
Python
284 lines
9.7 KiB
Python
"""
|
|
Blog Writer SEO Analysis API Endpoint
|
|
|
|
Provides API endpoint for analyzing blog content SEO with parallel processing
|
|
and CopilotKit integration for real-time progress updates.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
|
from pydantic import BaseModel
|
|
from typing import Dict, Any, Optional
|
|
from loguru import logger
|
|
from datetime import datetime
|
|
|
|
from services.blog_writer.seo.blog_content_seo_analyzer import BlogContentSEOAnalyzer
|
|
from services.blog_writer.core.blog_writer_service import BlogWriterService
|
|
|
|
|
|
router = APIRouter(prefix="/api/blog-writer/seo", tags=["Blog SEO Analysis"])
|
|
|
|
|
|
class SEOAnalysisRequest(BaseModel):
|
|
"""Request model for SEO analysis"""
|
|
blog_content: str
|
|
blog_title: Optional[str] = None
|
|
research_data: Dict[str, Any]
|
|
user_id: Optional[str] = None
|
|
session_id: Optional[str] = None
|
|
|
|
|
|
class SEOAnalysisResponse(BaseModel):
|
|
"""Response model for SEO analysis"""
|
|
success: bool
|
|
analysis_id: str
|
|
overall_score: float
|
|
category_scores: Dict[str, float]
|
|
analysis_summary: Dict[str, Any]
|
|
actionable_recommendations: list
|
|
detailed_analysis: Optional[Dict[str, Any]] = None
|
|
visualization_data: Optional[Dict[str, Any]] = None
|
|
generated_at: str
|
|
error: Optional[str] = None
|
|
|
|
|
|
class SEOAnalysisProgress(BaseModel):
|
|
"""Progress update model for real-time updates"""
|
|
analysis_id: str
|
|
stage: str
|
|
progress: int
|
|
message: str
|
|
timestamp: str
|
|
|
|
|
|
# Initialize analyzer
|
|
seo_analyzer = BlogContentSEOAnalyzer()
|
|
blog_writer_service = BlogWriterService()
|
|
|
|
|
|
@router.post("/analyze", response_model=SEOAnalysisResponse)
|
|
async def analyze_blog_seo(request: SEOAnalysisRequest):
|
|
"""
|
|
Analyze blog content for SEO optimization
|
|
|
|
This endpoint performs comprehensive SEO analysis including:
|
|
- Content structure analysis
|
|
- Keyword optimization analysis
|
|
- Readability assessment
|
|
- Content quality evaluation
|
|
- AI-powered insights generation
|
|
|
|
Args:
|
|
request: SEOAnalysisRequest containing blog content and research data
|
|
|
|
Returns:
|
|
SEOAnalysisResponse with comprehensive analysis results
|
|
"""
|
|
try:
|
|
logger.info(f"Starting SEO analysis for blog content")
|
|
|
|
# Validate request
|
|
if not request.blog_content or not request.blog_content.strip():
|
|
raise HTTPException(status_code=400, detail="Blog content is required")
|
|
|
|
if not request.research_data:
|
|
raise HTTPException(status_code=400, detail="Research data is required")
|
|
|
|
# Generate analysis ID
|
|
import uuid
|
|
analysis_id = str(uuid.uuid4())
|
|
|
|
# Perform SEO analysis
|
|
analysis_results = await seo_analyzer.analyze_blog_content(
|
|
blog_content=request.blog_content,
|
|
research_data=request.research_data,
|
|
blog_title=request.blog_title
|
|
)
|
|
|
|
# Check for errors
|
|
if 'error' in analysis_results:
|
|
logger.error(f"SEO analysis failed: {analysis_results['error']}")
|
|
return SEOAnalysisResponse(
|
|
success=False,
|
|
analysis_id=analysis_id,
|
|
overall_score=0,
|
|
category_scores={},
|
|
analysis_summary={},
|
|
actionable_recommendations=[],
|
|
detailed_analysis=None,
|
|
visualization_data=None,
|
|
generated_at=analysis_results.get('generated_at', ''),
|
|
error=analysis_results['error']
|
|
)
|
|
|
|
# Return successful response
|
|
return SEOAnalysisResponse(
|
|
success=True,
|
|
analysis_id=analysis_id,
|
|
overall_score=analysis_results.get('overall_score', 0),
|
|
category_scores=analysis_results.get('category_scores', {}),
|
|
analysis_summary=analysis_results.get('analysis_summary', {}),
|
|
actionable_recommendations=analysis_results.get('actionable_recommendations', []),
|
|
detailed_analysis=analysis_results.get('detailed_analysis'),
|
|
visualization_data=analysis_results.get('visualization_data'),
|
|
generated_at=analysis_results.get('generated_at', '')
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"SEO analysis endpoint error: {e}")
|
|
raise HTTPException(status_code=500, detail=f"SEO analysis failed: {str(e)}")
|
|
|
|
|
|
@router.post("/analyze-with-progress")
|
|
async def analyze_blog_seo_with_progress(request: SEOAnalysisRequest):
|
|
"""
|
|
Analyze blog content for SEO with real-time progress updates
|
|
|
|
This endpoint provides real-time progress updates for CopilotKit integration.
|
|
It returns a stream of progress updates and final results.
|
|
|
|
Args:
|
|
request: SEOAnalysisRequest containing blog content and research data
|
|
|
|
Returns:
|
|
Generator yielding progress updates and final results
|
|
"""
|
|
try:
|
|
logger.info(f"Starting SEO analysis with progress for blog content")
|
|
|
|
# Validate request
|
|
if not request.blog_content or not request.blog_content.strip():
|
|
raise HTTPException(status_code=400, detail="Blog content is required")
|
|
|
|
if not request.research_data:
|
|
raise HTTPException(status_code=400, detail="Research data is required")
|
|
|
|
# Generate analysis ID
|
|
import uuid
|
|
analysis_id = str(uuid.uuid4())
|
|
|
|
# Yield progress updates
|
|
async def progress_generator():
|
|
try:
|
|
# Stage 1: Initialization
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="initialization",
|
|
progress=10,
|
|
message="Initializing SEO analysis...",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Stage 2: Keyword extraction
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="keyword_extraction",
|
|
progress=20,
|
|
message="Extracting keywords from research data...",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Stage 3: Non-AI analysis
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="non_ai_analysis",
|
|
progress=40,
|
|
message="Running content structure and readability analysis...",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Stage 4: AI analysis
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="ai_analysis",
|
|
progress=70,
|
|
message="Generating AI-powered insights...",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Stage 5: Results compilation
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="compilation",
|
|
progress=90,
|
|
message="Compiling analysis results...",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Perform actual analysis
|
|
analysis_results = await seo_analyzer.analyze_blog_content(
|
|
blog_content=request.blog_content,
|
|
research_data=request.research_data
|
|
)
|
|
|
|
# Final result
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="completed",
|
|
progress=100,
|
|
message="SEO analysis completed successfully!",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
|
|
# Yield final results (can't return in async generator)
|
|
yield analysis_results
|
|
|
|
except Exception as e:
|
|
logger.error(f"Progress generator error: {e}")
|
|
yield SEOAnalysisProgress(
|
|
analysis_id=analysis_id,
|
|
stage="error",
|
|
progress=0,
|
|
message=f"Analysis failed: {str(e)}",
|
|
timestamp=datetime.utcnow().isoformat()
|
|
)
|
|
raise
|
|
|
|
return progress_generator()
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"SEO analysis with progress endpoint error: {e}")
|
|
raise HTTPException(status_code=500, detail=f"SEO analysis failed: {str(e)}")
|
|
|
|
|
|
@router.get("/analysis/{analysis_id}")
|
|
async def get_analysis_result(analysis_id: str):
|
|
"""
|
|
Get SEO analysis result by ID
|
|
|
|
Args:
|
|
analysis_id: Unique identifier for the analysis
|
|
|
|
Returns:
|
|
SEO analysis results
|
|
"""
|
|
try:
|
|
# In a real implementation, you would store results in a database
|
|
# For now, we'll return a placeholder
|
|
logger.info(f"Retrieving SEO analysis result for ID: {analysis_id}")
|
|
|
|
return {
|
|
"analysis_id": analysis_id,
|
|
"status": "completed",
|
|
"message": "Analysis results retrieved successfully"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Get analysis result error: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Failed to retrieve analysis result: {str(e)}")
|
|
|
|
|
|
@router.get("/health")
|
|
async def health_check():
|
|
"""Health check endpoint for SEO analysis service"""
|
|
return {
|
|
"status": "healthy",
|
|
"service": "blog-seo-analysis",
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
|