feat(seo-copilot): caching + freshness UI; glassomorphic styling; CopilotKit HITL modular actions; provider fixes; DB sessions & action types; seed 17 actions

This commit is contained in:
ajaysi
2025-08-30 16:12:41 +05:30
parent d9833f30a6
commit f5f3c09ecc
39 changed files with 10606 additions and 1606 deletions

View File

@@ -0,0 +1 @@
# Makes the middleware directory a Python package

View File

@@ -2,7 +2,7 @@
Database models for SEO analysis data storage
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, JSON, Float, Boolean, ForeignKey
from sqlalchemy import Column, Integer, String, DateTime, Text, JSON, Float, Boolean, ForeignKey, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
@@ -10,6 +10,82 @@ from typing import Dict, Any, List
Base = declarative_base()
class SEOActionType(Base):
"""Catalog of supported SEO action types (17 actions)."""
__tablename__ = 'seo_action_types'
id = Column(Integer, primary_key=True, index=True)
code = Column(String(100), unique=True, nullable=False) # e.g., analyze_page_speed
name = Column(String(200), nullable=False)
category = Column(String(50), nullable=True) # content, technical, performance, etc.
description = Column(Text, nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
def __repr__(self):
return f"<SEOActionType(code='{self.code}', category='{self.category}')>"
class SEOAnalysisSession(Base):
"""Anchor session for a set of SEO actions and summary."""
__tablename__ = 'seo_analysis_sessions'
id = Column(Integer, primary_key=True, index=True)
url = Column(String(500), nullable=False, index=True)
triggered_by_user_id = Column(String(64), nullable=True)
trigger_source = Column(String(32), nullable=True) # manual, schedule, action_followup, system
input_context = Column(JSON, nullable=True)
status = Column(String(20), default='success') # queued, running, success, failed, cancelled
started_at = Column(DateTime, default=func.now(), nullable=False)
completed_at = Column(DateTime, nullable=True)
summary = Column(Text, nullable=True)
overall_score = Column(Integer, nullable=True)
health_label = Column(String(50), nullable=True)
metrics = Column(JSON, nullable=True)
issues_overview = Column(JSON, nullable=True)
# Relationships
action_runs = relationship("SEOActionRun", back_populates="session", cascade="all, delete-orphan")
analyses = relationship("SEOAnalysis", back_populates="session", cascade="all, delete-orphan")
def __repr__(self):
return f"<SEOAnalysisSession(url='{self.url}', status='{self.status}')>"
class SEOActionRun(Base):
"""Each execution of a specific action (one of the 17)."""
__tablename__ = 'seo_action_runs'
id = Column(Integer, primary_key=True, index=True)
session_id = Column(Integer, ForeignKey('seo_analysis_sessions.id'), nullable=False)
action_type_id = Column(Integer, ForeignKey('seo_action_types.id'), nullable=False)
triggered_by_user_id = Column(String(64), nullable=True)
input_params = Column(JSON, nullable=True)
status = Column(String(20), default='success')
started_at = Column(DateTime, default=func.now(), nullable=False)
completed_at = Column(DateTime, nullable=True)
result_summary = Column(Text, nullable=True)
result = Column(JSON, nullable=True)
diagnostics = Column(JSON, nullable=True)
# Relationships
session = relationship("SEOAnalysisSession", back_populates="action_runs")
action_type = relationship("SEOActionType")
def __repr__(self):
return f"<SEOActionRun(action_type_id={self.action_type_id}, status='{self.status}')>"
class SEOActionRunLink(Base):
"""Graph relations between action runs for narrative linkage."""
__tablename__ = 'seo_action_run_links'
id = Column(Integer, primary_key=True, index=True)
from_action_run_id = Column(Integer, ForeignKey('seo_action_runs.id'), nullable=False)
to_action_run_id = Column(Integer, ForeignKey('seo_action_runs.id'), nullable=False)
relation = Column(String(50), nullable=False) # followup_of, supports, caused_by
created_at = Column(DateTime, default=func.now())
def __repr__(self):
return f"<SEOActionRunLink(relation='{self.relation}')>"
class SEOAnalysis(Base):
"""Main SEO analysis record"""
__tablename__ = 'seo_analyses'
@@ -20,12 +96,14 @@ class SEOAnalysis(Base):
health_status = Column(String(50), nullable=False) # excellent, good, needs_improvement, poor, error
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False)
analysis_data = Column(JSON, nullable=True) # Store complete analysis data
session_id = Column(Integer, ForeignKey('seo_analysis_sessions.id'), nullable=True)
# Relationships
critical_issues = relationship("SEOIssue", back_populates="analysis", cascade="all, delete-orphan")
warnings = relationship("SEOWarning", back_populates="analysis", cascade="all, delete-orphan")
recommendations = relationship("SEORecommendation", back_populates="analysis", cascade="all, delete-orphan")
category_scores = relationship("SEOCategoryScore", back_populates="analysis", cascade="all, delete-orphan")
session = relationship("SEOAnalysisSession", back_populates="analyses")
def __repr__(self):
return f"<SEOAnalysis(url='{self.url}', score={self.overall_score}, status='{self.health_status}')>"
@@ -36,6 +114,8 @@ class SEOIssue(Base):
id = Column(Integer, primary_key=True, index=True)
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
session_id = Column(Integer, ForeignKey('seo_analysis_sessions.id'), nullable=True)
action_run_id = Column(Integer, ForeignKey('seo_action_runs.id'), nullable=True)
issue_text = Column(Text, nullable=False)
category = Column(String(100), nullable=True) # url_structure, meta_data, content, etc.
priority = Column(String(20), default='critical') # critical, high, medium, low
@@ -53,6 +133,8 @@ class SEOWarning(Base):
id = Column(Integer, primary_key=True, index=True)
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
session_id = Column(Integer, ForeignKey('seo_analysis_sessions.id'), nullable=True)
action_run_id = Column(Integer, ForeignKey('seo_action_runs.id'), nullable=True)
warning_text = Column(Text, nullable=False)
category = Column(String(100), nullable=True)
priority = Column(String(20), default='medium')
@@ -70,6 +152,8 @@ class SEORecommendation(Base):
id = Column(Integer, primary_key=True, index=True)
analysis_id = Column(Integer, ForeignKey('seo_analyses.id'), nullable=False)
session_id = Column(Integer, ForeignKey('seo_analysis_sessions.id'), nullable=True)
action_run_id = Column(Integer, ForeignKey('seo_action_runs.id'), nullable=True)
recommendation_text = Column(Text, nullable=False)
category = Column(String(100), nullable=True)
difficulty = Column(String(20), default='medium') # easy, medium, hard

View File

@@ -0,0 +1,165 @@
"""
Seed the seo_action_types table with the canonical set of SEO actions.
Run (from backend/):
python scripts/seed_seo_action_types.py
"""
from typing import List, Dict
from loguru import logger
import sys, os
# Ensure backend/ is on sys.path when running as a script
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
BACKEND_ROOT = os.path.abspath(os.path.join(CURRENT_DIR, os.pardir))
if BACKEND_ROOT not in sys.path:
sys.path.insert(0, BACKEND_ROOT)
from services.database import init_database, get_db_session
from models.seo_analysis import SEOActionType
def get_actions() -> List[Dict]:
return [
{
"code": "analyze_seo_comprehensive",
"name": "Analyze SEO (Comprehensive)",
"category": "analysis",
"description": "Perform a comprehensive SEO analysis across technical, on-page, and performance.",
},
{
"code": "generate_meta_descriptions",
"name": "Generate Meta Descriptions",
"category": "content",
"description": "Generate optimized meta description suggestions for pages.",
},
{
"code": "analyze_page_speed",
"name": "Analyze Page Speed",
"category": "performance",
"description": "Run page speed and Core Web Vitals checks for mobile/desktop.",
},
{
"code": "analyze_sitemap",
"name": "Analyze Sitemap",
"category": "discovery",
"description": "Analyze sitemap structure, coverage, and publishing patterns.",
},
{
"code": "generate_image_alt_text",
"name": "Generate Image Alt Text",
"category": "content",
"description": "Propose SEO-friendly alt text for images.",
},
{
"code": "generate_opengraph_tags",
"name": "Generate OpenGraph Tags",
"category": "content",
"description": "Create OpenGraph/Twitter meta tags for better social previews.",
},
{
"code": "analyze_on_page_seo",
"name": "Analyze On-Page SEO",
"category": "on_page",
"description": "Audit titles, headings, keyword usage, and internal links.",
},
{
"code": "analyze_technical_seo",
"name": "Analyze Technical SEO",
"category": "technical",
"description": "Audit crawlability, canonicals, schema, security, and redirects.",
},
{
"code": "analyze_enterprise_seo",
"name": "Analyze Enterprise SEO",
"category": "enterprise",
"description": "Advanced enterprise-level audits and recommendations.",
},
{
"code": "analyze_content_strategy",
"name": "Analyze Content Strategy",
"category": "content",
"description": "Analyze content themes, gaps, and strategy effectiveness.",
},
{
"code": "perform_website_audit",
"name": "Perform Website Audit",
"category": "analysis",
"description": "Holistic website audit with prioritized issues and actions.",
},
{
"code": "analyze_content_comprehensive",
"name": "Analyze Content (Comprehensive)",
"category": "content",
"description": "Deep content analysis including readability and structure.",
},
{
"code": "check_seo_health",
"name": "Check SEO Health",
"category": "analysis",
"description": "Quick health check and score snapshot.",
},
{
"code": "explain_seo_concept",
"name": "Explain SEO Concept",
"category": "education",
"description": "Explain SEO concepts in simple terms with examples.",
},
{
"code": "update_seo_charts",
"name": "Update SEO Charts",
"category": "visualization",
"description": "Update dashboard charts and visualizations per user request.",
},
{
"code": "customize_seo_dashboard",
"name": "Customize SEO Dashboard",
"category": "visualization",
"description": "Modify dashboard layout, widgets, and focus areas.",
},
{
"code": "analyze_seo_full",
"name": "Analyze SEO (Full)",
"category": "analysis",
"description": "Full analysis variant (alternate flow or endpoint).",
},
]
def seed_action_types():
init_database()
db = get_db_session()
if db is None:
raise RuntimeError("Could not get DB session")
try:
actions = get_actions()
created, updated, skipped = 0, 0, 0
for action in actions:
existing = db.query(SEOActionType).filter(SEOActionType.code == action["code"]).one_or_none()
if existing:
# Update name/category/description if changed
changed = False
if existing.name != action["name"]:
existing.name = action["name"]; changed = True
if existing.category != action["category"]:
existing.category = action["category"]; changed = True
if existing.description != action["description"]:
existing.description = action["description"]; changed = True
if changed:
updated += 1
else:
skipped += 1
else:
db.add(SEOActionType(**action))
created += 1
db.commit()
logger.info(f"SEO action types seeding done. created={created}, updated={updated}, unchanged={skipped}")
finally:
db.close()
if __name__ == "__main__":
seed_action_types()

View File

@@ -1,12 +1,7 @@
"""
AI SEO Tools Services Package
This package contains all migrated SEO tools as FastAPI services.
Each service provides structured, AI-enhanced SEO analysis capabilities.
"""
# SEO tools package initializer
from .meta_description_service import MetaDescriptionService
from .pagespeed_service import PageSpeedService
from .pagespeed_service import PageSpeedService
from .sitemap_service import SitemapService
from .image_alt_service import ImageAltService
from .opengraph_service import OpenGraphService
@@ -16,13 +11,13 @@ from .enterprise_seo_service import EnterpriseSEOService
from .content_strategy_service import ContentStrategyService
__all__ = [
"MetaDescriptionService",
"PageSpeedService",
"SitemapService",
"ImageAltService",
"OpenGraphService",
"OnPageSEOService",
"TechnicalSEOService",
"EnterpriseSEOService",
"ContentStrategyService"
'MetaDescriptionService',
'PageSpeedService',
'SitemapService',
'ImageAltService',
'OpenGraphService',
'OnPageSEOService',
'TechnicalSEOService',
'EnterpriseSEOService',
'ContentStrategyService',
]

View File

@@ -10,7 +10,7 @@ from datetime import datetime
from loguru import logger
from ..llm_providers.main_text_generation import llm_text_gen
from ...middleware.logging_middleware import seo_logger
from middleware.logging_middleware import seo_logger
class MetaDescriptionService:

View File

@@ -13,7 +13,7 @@ from loguru import logger
import os
from ..llm_providers.main_text_generation import llm_text_gen
from ...middleware.logging_middleware import seo_logger
from middleware.logging_middleware import seo_logger
class PageSpeedService:

View File

@@ -15,7 +15,7 @@ from urllib.parse import urlparse, urljoin
import pandas as pd
from ..llm_providers.main_text_generation import llm_text_gen
from ...middleware.logging_middleware import seo_logger
from middleware.logging_middleware import seo_logger
class SitemapService: