Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts

This commit is contained in:
ajaysi
2026-02-08 13:56:57 +05:30
parent 1db10ccd0f
commit e404a86502
333 changed files with 42223 additions and 10875 deletions

View File

@@ -35,6 +35,7 @@ from models.blog_models import (
)
from services.blog_writer.blog_service import BlogWriterService
from services.blog_writer.seo.blog_seo_recommendation_applier import BlogSEORecommendationApplier
from services.llm_providers.main_text_generation import llm_text_gen
from .task_manager import task_manager
from .cache_manager import cache_manager
from models.blog_models import MediumBlogGenerateRequest
@@ -97,6 +98,217 @@ async def apply_seo_recommendations(
raise HTTPException(status_code=500, detail=str(e))
class BlogSectionToolRequest(BaseModel):
section_id: str = Field(..., description="Section id in blog writer UI")
title: Optional[str] = Field(default=None, description="Section title/heading")
content: str = Field(..., description="Section content text")
keywords: List[str] = Field(default_factory=list, description="Optional target keywords")
goal: Optional[str] = Field(default=None, description="Optional optimization goal")
@router.post("/section/tools/originality")
async def section_originality_tools(
request: BlogSectionToolRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
try:
if not current_user:
raise HTTPException(status_code=401, detail="Authentication required")
user_id = str(current_user.get("id"))
if not user_id:
raise HTTPException(status_code=401, detail="User ID not found in authentication token")
from services.intelligence.sif_integration import SIFIntegrationService
from services.intelligence.sif_agents import ContentGuardianAgent
sif_service = SIFIntegrationService(user_id)
intelligence = sif_service.intelligence_service
content = (request.content or "").strip()
if len(content) < 50:
return {
"success": False,
"section_id": request.section_id,
"error": "Content too short for originality check",
"matches": [],
}
matches = await intelligence.search(content, limit=5)
normalized_matches = []
for m in matches or []:
normalized_matches.append(
{
"id": m.get("id"),
"score": m.get("score", 0.0),
"excerpt": (m.get("text", "") or "")[:240],
}
)
guardian = ContentGuardianAgent(intelligence, sif_service=sif_service)
cannibalization = await guardian.check_cannibalization(content)
return {
"success": True,
"section_id": request.section_id,
"cannibalization": cannibalization,
"matches": normalized_matches,
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to run originality tools: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/section/tools/internal-links")
async def section_internal_link_tools(
request: BlogSectionToolRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
try:
if not current_user:
raise HTTPException(status_code=401, detail="Authentication required")
user_id = str(current_user.get("id"))
if not user_id:
raise HTTPException(status_code=401, detail="User ID not found in authentication token")
from services.intelligence.sif_integration import SIFIntegrationService
from services.intelligence.sif_agents import LinkGraphAgent
sif_service = SIFIntegrationService(user_id)
intelligence = sif_service.intelligence_service
content = (request.content or "").strip()
suggestions = []
if len(content) >= 50:
link_agent = LinkGraphAgent(intelligence, sif_service=sif_service)
suggestions = await link_agent.link_suggester(content)
return {
"success": True,
"section_id": request.section_id,
"suggestions": suggestions or [],
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to run internal link tools: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/section/tools/fact-check")
async def section_fact_check_tools(
request: BlogSectionToolRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
try:
if not current_user:
raise HTTPException(status_code=401, detail="Authentication required")
user_id = str(current_user.get("id"))
if not user_id:
raise HTTPException(status_code=401, detail="User ID not found in authentication token")
from services.intelligence.sif_integration import SIFIntegrationService
from services.intelligence.sif_agents import CitationExpert
sif_service = SIFIntegrationService(user_id)
intelligence = sif_service.intelligence_service
expert = CitationExpert(intelligence)
content = (request.content or "").strip()
verification = await expert.claim_verifier(content)
topic = request.title or content[:120]
citations = await expert.citation_finder(topic)
return {
"success": True,
"section_id": request.section_id,
"verification": verification,
"citations": citations or [],
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to run fact check tools: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/section/tools/optimize")
async def section_optimize_tools(
request: BlogSectionToolRequest,
current_user: Dict[str, Any] = Depends(get_current_user),
) -> Dict[str, Any]:
try:
if not current_user:
raise HTTPException(status_code=401, detail="Authentication required")
user_id = str(current_user.get("id"))
if not user_id:
raise HTTPException(status_code=401, detail="User ID not found in authentication token")
content = (request.content or "").strip()
if len(content) < 50:
return {
"success": False,
"section_id": request.section_id,
"error": "Content too short for optimization",
}
goal = request.goal or "readability"
keywords_str = ", ".join(request.keywords or [])
system_prompt = (
"You are an expert editor. Optimize the provided blog section while preserving meaning and tone."
)
prompt = (
f"Optimization goal: {goal}\n"
f"Target keywords (if any): {keywords_str}\n"
f"Section title: {request.title or ''}\n\n"
"Return a JSON object with keys:\n"
'- optimized_content: string\n'
'- changes_made: array of strings\n'
"- diff_summary: string\n\n"
f"Section content:\n{content}\n"
)
json_struct = {
"type": "object",
"properties": {
"optimized_content": {"type": "string"},
"changes_made": {"type": "array", "items": {"type": "string"}},
"diff_summary": {"type": "string"},
},
"required": ["optimized_content", "changes_made", "diff_summary"],
}
raw = llm_text_gen(prompt=prompt, system_prompt=system_prompt, json_struct=json_struct, user_id=user_id)
data = None
try:
import json as _json
data = _json.loads(raw) if isinstance(raw, str) else raw
except Exception:
data = {
"optimized_content": raw,
"changes_made": ["Optimization applied"],
"diff_summary": "Generated optimized version",
}
return {
"success": True,
"section_id": request.section_id,
"optimized_content": data.get("optimized_content"),
"changes_made": data.get("changes_made", []),
"diff_summary": data.get("diff_summary"),
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to run optimize tools: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/health")
async def health() -> Dict[str, Any]:
@@ -286,7 +498,8 @@ async def generate_section(
) -> BlogSectionResponse:
"""Generate content for a specific section."""
try:
response = await service.generate_section(request)
user_id = str(current_user.get('id', '')) if current_user else None
response = await service.generate_section(request, user_id=user_id)
# Save and track text content (non-blocking)
if response.markdown:
@@ -981,4 +1194,4 @@ async def generate_introductions(
raise
except Exception as e:
logger.error(f"Failed to generate introductions: {e}")
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -10,10 +10,14 @@ from pydantic import BaseModel
from typing import Dict, Any, Optional
from loguru import logger
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import select
from services.blog_writer.seo.blog_content_seo_analyzer import BlogContentSEOAnalyzer
from services.blog_writer.core.blog_writer_service import BlogWriterService
from middleware.auth_middleware import get_current_user
from services.database import get_db
from models.seo_analysis import SEOAnalysis
router = APIRouter(prefix="/api/blog-writer/seo", tags=["Blog SEO Analysis"])
@@ -147,7 +151,8 @@ async def analyze_blog_seo(
@router.post("/analyze-with-progress")
async def analyze_blog_seo_with_progress(
request: SEOAnalysisRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
Analyze blog content for SEO with real-time progress updates
@@ -158,6 +163,7 @@ async def analyze_blog_seo_with_progress(
Args:
request: SEOAnalysisRequest containing blog content and research data
current_user: Authenticated user from middleware
db: Database session
Returns:
Generator yielding progress updates and final results
@@ -240,6 +246,35 @@ async def analyze_blog_seo_with_progress(
user_id=user_id
)
# Save to Database
try:
draft_url = f"draft:{analysis_id}"
overall_score = analysis_results.get('overall_score', 0)
# Determine health status
if overall_score >= 90:
health_status = "excellent"
elif overall_score >= 70:
health_status = "good"
elif overall_score >= 50:
health_status = "needs_improvement"
else:
health_status = "poor"
new_analysis = SEOAnalysis(
url=draft_url,
overall_score=int(overall_score),
health_status=health_status,
timestamp=datetime.utcnow(),
analysis_data=analysis_results
)
db.add(new_analysis)
db.commit()
logger.info(f"Saved SEO analysis results to DB for ID: {analysis_id}")
except Exception as db_error:
logger.error(f"Failed to save analysis to DB: {db_error}")
# Continue without failing
# Final result
yield SEOAnalysisProgress(
analysis_id=analysis_id,
@@ -273,27 +308,46 @@ async def analyze_blog_seo_with_progress(
@router.get("/analysis/{analysis_id}")
async def get_analysis_result(analysis_id: str):
async def get_analysis_result(
analysis_id: str,
db: Session = Depends(get_db)
):
"""
Get SEO analysis result by ID
Args:
analysis_id: Unique identifier for the analysis
db: Database session
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"
}
# Look for the analysis in the database
draft_url = f"draft:{analysis_id}"
stmt = select(SEOAnalysis).where(SEOAnalysis.url == draft_url)
analysis = db.execute(stmt).scalar_one_or_none()
if analysis and analysis.analysis_data:
# Return stored analysis data
return {
"analysis_id": analysis_id,
"status": "completed",
"message": "Analysis results retrieved successfully",
**analysis.analysis_data
}
# If not found in DB (fallback for legacy or in-memory only)
# For now, we return 404 to encourage DB usage, or we could return a placeholder if strictly needed.
# But user requested DB integration, so we should rely on DB.
logger.warning(f"Analysis result not found in DB for ID: {analysis_id}")
raise HTTPException(status_code=404, detail="Analysis result not found")
except HTTPException:
raise
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)}")

View File

@@ -12,6 +12,8 @@ from datetime import datetime
from typing import Any, Dict, List
from fastapi import HTTPException
from loguru import logger
from sqlalchemy.orm import Session
from services.database import SessionLocal, get_session_for_user
from models.blog_models import (
BlogResearchRequest,
@@ -261,11 +263,17 @@ class TaskManager:
if total_target > 1000:
raise ValueError("Global target words exceed 1000; medium generation not allowed")
result: MediumBlogGenerateResult = await self.service.generate_medium_blog_with_progress(
request,
task_id,
user_id
)
# Create a sync session for asset saving
db_session = SessionLocal()
try:
result: MediumBlogGenerateResult = await self.service.generate_medium_blog_with_progress(
request,
task_id,
user_id,
db=db_session
)
finally:
db_session.close()
if not result or not getattr(result, "sections", None):
raise ValueError("Empty generation result from model")